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

mappjzc 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 b40efa574 feat: add deep search for project (#5524)
b40efa574 is described below

commit b40efa574abbe0530d3cf623cbddb5a3e34bcc7f
Author: mappjzc <[email protected]>
AuthorDate: Tue Jun 20 16:38:02 2023 +0800

    feat: add deep search for project (#5524)
    
    Add deepsearch on bug.
    Add deepsearch on story.
    Fix e2e test and unit test.
    
    Nddtfjiang <[email protected]>
---
 backend/plugins/zentao/api/blueprint_V200_test.go  |  24 ++---
 backend/plugins/zentao/api/blueprint_v200.go       |  44 ++++-----
 backend/plugins/zentao/api/remote.go               |  50 +++++-----
 backend/plugins/zentao/api/scope.go                | 102 ++++++++++++++++++++-
 backend/plugins/zentao/e2e/account_test.go         |   7 +-
 backend/plugins/zentao/e2e/bug_commits_test.go     |   4 +-
 backend/plugins/zentao/e2e/bug_test.go             |   9 +-
 backend/plugins/zentao/e2e/execution_test.go       |   4 +-
 backend/plugins/zentao/e2e/product_test.go         |   4 +-
 .../e2e/snapshot_tables/_tool_zentao_bugs.csv      |   3 -
 .../e2e/snapshot_tables/_tool_zentao_stories.csv   |   2 -
 .../e2e/snapshot_tables/board_issues_bug.csv       |   1 -
 .../e2e/snapshot_tables/board_issues_story.csv     |   2 -
 .../e2e/snapshot_tables/bug_issue_assignees.csv    |   1 -
 .../zentao/e2e/snapshot_tables/issues_bug.csv      |   1 -
 .../zentao/e2e/snapshot_tables/issues_story.csv    |   2 -
 .../e2e/snapshot_tables/story_issue_assignees.csv  |   2 -
 backend/plugins/zentao/e2e/story_commits_test.go   |   4 +-
 backend/plugins/zentao/e2e/story_test.go           |  11 ++-
 backend/plugins/zentao/e2e/task_test.go            |   2 +
 backend/plugins/zentao/impl/impl.go                |  97 ++++++++++++--------
 backend/plugins/zentao/models/connection.go        |   2 +-
 backend/plugins/zentao/tasks/bug_collector.go      |   4 +
 .../plugins/zentao/tasks/bug_commits_collector.go  |   4 +
 .../plugins/zentao/tasks/bug_commits_extractor.go  |   4 +
 backend/plugins/zentao/tasks/bug_convertor.go      |   4 +
 backend/plugins/zentao/tasks/bug_extractor.go      |  12 +++
 .../zentao/tasks/bug_repo_commits_collector.go     |   4 +
 .../zentao/tasks/bug_repo_commits_convertor.go     |   4 +
 .../zentao/tasks/bug_repo_commits_extractor.go     |   4 +
 .../plugins/zentao/tasks/execution_extractor.go    |   6 ++
 backend/plugins/zentao/tasks/product_convertor.go  |   2 +-
 backend/plugins/zentao/tasks/shared.go             |  17 ++++
 backend/plugins/zentao/tasks/story_collector.go    |   4 +
 .../zentao/tasks/story_commits_collector.go        |   4 +
 .../zentao/tasks/story_commits_extractor.go        |   4 +
 backend/plugins/zentao/tasks/story_convertor.go    |   4 +
 backend/plugins/zentao/tasks/story_extractor.go    |  12 +++
 .../zentao/tasks/story_repo_commits_collector.go   |   4 +
 .../zentao/tasks/story_repo_commits_convertor.go   |   4 +
 .../zentao/tasks/story_repo_commits_extractor.go   |   4 +
 backend/plugins/zentao/tasks/task_data.go          |   3 +
 backend/plugins/zentao/tasks/task_extractor.go     |   5 +
 43 files changed, 368 insertions(+), 123 deletions(-)

diff --git a/backend/plugins/zentao/api/blueprint_V200_test.go 
b/backend/plugins/zentao/api/blueprint_V200_test.go
index 64d93be67..5d8cddf95 100644
--- a/backend/plugins/zentao/api/blueprint_V200_test.go
+++ b/backend/plugins/zentao/api/blueprint_V200_test.go
@@ -64,11 +64,11 @@ func TestMakeDataSourcePipelinePlanV200(t *testing.T) {
        bs := &plugin.BlueprintScopeV200{
                Id: "project/1",
        }
-       bs2 := &plugin.BlueprintScopeV200{
+       /*bs2 := &plugin.BlueprintScopeV200{
                Id: "product/1",
-       }
+       }*/
        bpScopes := make([]*plugin.BlueprintScopeV200, 0)
-       bpScopes = append(bpScopes, bs, bs2)
+       bpScopes = append(bpScopes, bs)
        syncPolicy := &plugin.BlueprintSyncPolicy{}
 
        plan := make(plugin.PipelinePlan, len(bpScopes))
@@ -87,7 +87,7 @@ func TestMakeDataSourcePipelinePlanV200(t *testing.T) {
                                },
                        },
                },
-               plugin.PipelineStage{
+               /*plugin.PipelineStage{
                        {
                                Plugin:   "zentao",
                                Subtasks: []string{},
@@ -97,7 +97,7 @@ func TestMakeDataSourcePipelinePlanV200(t *testing.T) {
                                        "projectId":    int64(0),
                                },
                        },
-               },
+               },*/
        }
        assert.Equal(t, expectPlan, plan)
        expectScopes := make([]plugin.Scope, 0)
@@ -111,7 +111,7 @@ func TestMakeDataSourcePipelinePlanV200(t *testing.T) {
                CreatedDate: nil,
                Type:        `project`,
        }
-       scopeTicket2 := &ticket.Board{
+       /*scopeTicket2 := &ticket.Board{
                DomainEntity: domainlayer.DomainEntity{
                        Id: "zentao:ZentaoProduct:1:1",
                },
@@ -120,21 +120,21 @@ func TestMakeDataSourcePipelinePlanV200(t *testing.T) {
                Url:         "",
                CreatedDate: nil,
                Type:        `product/normal`,
-       }
+       }*/
 
-       expectScopes = append(expectScopes, scopeTicket1, scopeTicket2)
+       expectScopes = append(expectScopes, scopeTicket1)
        assert.Equal(t, expectScopes, scopes)
 }
 
 // mockBasicRes FIXME ...
 func mockBasicRes(t *testing.T) {
-       testZentaoProduct := &models.ZentaoProduct{
+       /*testZentaoProduct := &models.ZentaoProduct{
                ConnectionId:  1,
                Id:            1,
                Name:          "test/testRepo",
                Type:          `product/normal`,
                ScopeConfigId: 0,
-       }
+       }*/
        testZentaoProject := &models.ZentaoProject{
                ConnectionId:  1,
                Id:            1,
@@ -148,10 +148,10 @@ func mockBasicRes(t *testing.T) {
                        *dst = *testZentaoProject
                }).Return(nil)
 
-               mockDal.On("First", 
mock.AnythingOfType("*models.ZentaoProduct"), mock.Anything).Run(func(args 
mock.Arguments) {
+               /*mockDal.On("First", 
mock.AnythingOfType("*models.ZentaoProduct"), mock.Anything).Run(func(args 
mock.Arguments) {
                        dst := args.Get(0).(*models.ZentaoProduct)
                        *dst = *testZentaoProduct
-               }).Return(nil)
+               }).Return(nil)*/
 
                mockDal.On("First", 
mock.AnythingOfType("*models.ZentaoScopeConfig"), mock.Anything).Run(func(args 
mock.Arguments) {
                        panic("The empty scope should not call First() for 
ZentaoScopeConfig")
diff --git a/backend/plugins/zentao/api/blueprint_v200.go 
b/backend/plugins/zentao/api/blueprint_v200.go
index 350b4b35e..817a8c338 100644
--- a/backend/plugins/zentao/api/blueprint_v200.go
+++ b/backend/plugins/zentao/api/blueprint_v200.go
@@ -18,7 +18,6 @@ limitations under the License.
 package api
 
 import (
-       "strings"
        "time"
 
        "github.com/apache/incubator-devlake/core/errors"
@@ -67,30 +66,31 @@ func makePipelinePlanV200(
                        ConnectionId: connection.ID,
                }
 
-               scopeType := strings.Split(bpScope.Id, `/`)[0]
-               scopeId := strings.Split(bpScope.Id, `/`)[1]
+               //scopeType := strings.Split(bpScope.Id, `/`)[0]
+               //scopeId := strings.Split(bpScope.Id, `/`)[1]
+               scopeId := bpScope.Id
 
                var entities []string
 
-               if scopeType == `project` {
-                       project, scopeConfig, err := 
projectScopeHelper.DbHelper().GetScopeAndConfig(connection.ID, scopeId)
-                       if err != nil {
-                               return nil, nil, err
-                       }
-                       op.ProjectId = project.Id
-                       entities = scopeConfig.Entities
-
-                       if utils.StringsContains(entities, 
plugin.DOMAIN_TYPE_TICKET) {
-                               scopeTicket := &ticket.Board{
-                                       DomainEntity: domainlayer.DomainEntity{
-                                               Id: 
didgen.NewDomainIdGenerator(&models.ZentaoProject{}).Generate(connection.ID, 
project.Id),
-                                       },
-                                       Name: project.Name,
-                                       Type: project.Type,
-                               }
-                               domainScopes = append(domainScopes, scopeTicket)
+               //if scopeType == `project` {
+               project, scopeConfig, err := 
projectScopeHelper.DbHelper().GetScopeAndConfig(connection.ID, scopeId)
+               if err != nil {
+                       return nil, nil, err
+               }
+               op.ProjectId = project.Id
+               entities = scopeConfig.Entities
+
+               if utils.StringsContains(entities, plugin.DOMAIN_TYPE_TICKET) {
+                       scopeTicket := &ticket.Board{
+                               DomainEntity: domainlayer.DomainEntity{
+                                       Id: 
didgen.NewDomainIdGenerator(&models.ZentaoProject{}).Generate(connection.ID, 
project.Id),
+                               },
+                               Name: project.Name,
+                               Type: project.Type,
                        }
-               } else {
+                       domainScopes = append(domainScopes, scopeTicket)
+               }
+               /*} else {
                        product, scopeConfig, err := 
productScopeHelper.DbHelper().GetScopeAndConfig(connection.ID, scopeId)
                        if err != nil {
                                return nil, nil, err
@@ -108,7 +108,7 @@ func makePipelinePlanV200(
                                }
                                domainScopes = append(domainScopes, scopeTicket)
                        }
-               }
+               }*/
 
                if syncPolicy.TimeAfter != nil {
                        op.TimeAfter = syncPolicy.TimeAfter.Format(time.RFC3339)
diff --git a/backend/plugins/zentao/api/remote.go 
b/backend/plugins/zentao/api/remote.go
index 795be8a1b..bf3328102 100644
--- a/backend/plugins/zentao/api/remote.go
+++ b/backend/plugins/zentao/api/remote.go
@@ -83,30 +83,32 @@ func RemoteScopes(input *plugin.ApiResourceInput) 
(*plugin.ApiResourceOutput, er
        if gid == "" {
                return productRemoteHelper.GetScopesFromRemote(input, getGroup, 
nil)
        } else if gid == `products` {
-               return productRemoteHelper.GetScopesFromRemote(input,
-                       nil,
-                       func(basicRes context2.BasicRes, gid string, queryData 
*api.RemoteQueryData, 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
-                       })
+               /*
+                       return productRemoteHelper.GetScopesFromRemote(input,
+                               nil,
+                               func(basicRes context2.BasicRes, gid string, 
queryData *api.RemoteQueryData, 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,
diff --git a/backend/plugins/zentao/api/scope.go 
b/backend/plugins/zentao/api/scope.go
index 2a9731f89..99a8e711f 100644
--- a/backend/plugins/zentao/api/scope.go
+++ b/backend/plugins/zentao/api/scope.go
@@ -68,6 +68,21 @@ func PutProjectScope(input *plugin.ApiResourceInput) 
(*plugin.ApiResourceOutput,
        return projectScopeHelper.Put(input)
 }
 
+// PutScope 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}/scopes [PUT]
+func PutScope(input *plugin.ApiResourceInput) (*plugin.ApiResourceOutput, 
errors.Error) {
+       return PutProjectScope(input)
+}
+
 // UpdateProductScope patch to zentao product
 // @Summary patch to zentao product
 // @Description patch to zentao product
@@ -100,7 +115,63 @@ func UpdateProjectScope(input *plugin.ApiResourceInput) 
(*plugin.ApiResourceOutp
        return projectScopeHelper.Update(input)
 }
 
-// TODO GetScopeList get zentao projects and products
+// UpdateScope 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/{scopeId} [PATCH]
+func UpdateScope(input *plugin.ApiResourceInput) (*plugin.ApiResourceOutput, 
errors.Error) {
+       return UpdateProjectScope(input)
+}
+
+// GetProductScopeList 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/gitlab/connections/{connectionId}/scopes/product/ [GET]
+func GetProductScopeList(input *plugin.ApiResourceInput) 
(*plugin.ApiResourceOutput, errors.Error) {
+       return productScopeHelper.GetScopeList(input)
+}
+
+// GetProjectScopeList get Gitlab projects
+// @Summary get Gitlab projects
+// @Description get Gitlab projects
+// @Tags plugins/gitlab
+// @Param connectionId path int false "connection ID"
+// @Param blueprints query bool false "also return blueprints using these 
scopes as part of the payload"
+// @Success 200  {object} []ProjectScopeRes
+// @Failure 400  {object} shared.ApiBody "Bad Request"
+// @Failure 500  {object} shared.ApiBody "Internal Error"
+// @Router /plugins/gitlab/connections/{connectionId}/scopes/project/ [GET]
+func GetProjectScopeList(input *plugin.ApiResourceInput) 
(*plugin.ApiResourceOutput, errors.Error) {
+       return projectScopeHelper.GetScopeList(input)
+}
+
+// GetScopeList get Gitlab projects
+// @Summary get Gitlab projects
+// @Description get Gitlab projects
+// @Tags plugins/gitlab
+// @Param connectionId path int false "connection ID"
+// @Param blueprints query bool false "also return blueprints using these 
scopes as part of the payload"
+// @Success 200  {object} []ProjectScopeRes
+// @Failure 400  {object} shared.ApiBody "Bad Request"
+// @Failure 500  {object} shared.ApiBody "Internal Error"
+// @Router /plugins/gitlab/connections/{connectionId}/scopes/ [GET]
+func GetScopeList(input *plugin.ApiResourceInput) (*plugin.ApiResourceOutput, 
errors.Error) {
+       return GetProjectScopeList(input)
+}
 
 // GetProductScope get one product
 // @Summary get one product
@@ -130,6 +201,20 @@ func GetProjectScope(input *plugin.ApiResourceInput) 
(*plugin.ApiResourceOutput,
        return projectScopeHelper.GetScope(input)
 }
 
+// GetScope 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/{scopeId} [GET]
+func GetScope(input *plugin.ApiResourceInput) (*plugin.ApiResourceOutput, 
errors.Error) {
+       return GetProjectScope(input)
+}
+
 // DeleteScope delete plugin data associated with the scope and optionally the 
scope itself
 // @Summary delete plugin data associated with the scope and optionally the 
scope itself
 // @Description delete data associated with plugin scope
@@ -161,3 +246,18 @@ func DeleteProductScope(input *plugin.ApiResourceInput) 
(*plugin.ApiResourceOutp
 func DeleteProjectScope(input *plugin.ApiResourceInput) 
(*plugin.ApiResourceOutput, errors.Error) {
        return projectScopeHelper.Delete(input)
 }
+
+// DeleteScope delete plugin data associated with the scope and optionally the 
scope itself
+// @Summary delete plugin data associated with the scope and optionally the 
scope itself
+// @Description delete data associated with plugin scope
+// @Tags plugins/zentao
+// @Param connectionId path int true "connection ID"
+// @Param scopeId path int true "scope ID"
+// @Param delete_data_only query bool false "Only delete the scope data, not 
the scope itself"
+// @Success 200
+// @Failure 400  {object} shared.ApiBody "Bad Request"
+// @Failure 500  {object} shared.ApiBody "Internal Error"
+// @Router /plugins/zentao/connections/{connectionId}/scopes/{scopeId} [DELETE]
+func DeleteScope(input *plugin.ApiResourceInput) (*plugin.ApiResourceOutput, 
errors.Error) {
+       return DeleteProjectScope(input)
+}
diff --git a/backend/plugins/zentao/e2e/account_test.go 
b/backend/plugins/zentao/e2e/account_test.go
index f92104046..9cf08065b 100644
--- a/backend/plugins/zentao/e2e/account_test.go
+++ b/backend/plugins/zentao/e2e/account_test.go
@@ -18,13 +18,14 @@ limitations under the License.
 package e2e
 
 import (
+       "testing"
+
        "github.com/apache/incubator-devlake/core/models/common"
        
"github.com/apache/incubator-devlake/core/models/domainlayer/crossdomain"
        "github.com/apache/incubator-devlake/helpers/e2ehelper"
        "github.com/apache/incubator-devlake/plugins/zentao/impl"
        "github.com/apache/incubator-devlake/plugins/zentao/models"
        "github.com/apache/incubator-devlake/plugins/zentao/tasks"
-       "testing"
 )
 
 func TestZentaoAccountDataFlow(t *testing.T) {
@@ -36,7 +37,9 @@ func TestZentaoAccountDataFlow(t *testing.T) {
                Options: &tasks.ZentaoOptions{
                        ConnectionId: 1,
                        ProjectId:    1,
-                       ProductId:    3,
+               },
+               ProductList: map[int64]string{
+                       3: "",
                },
        }
 
diff --git a/backend/plugins/zentao/e2e/bug_commits_test.go 
b/backend/plugins/zentao/e2e/bug_commits_test.go
index 5bd50fdc1..f30827e4f 100644
--- a/backend/plugins/zentao/e2e/bug_commits_test.go
+++ b/backend/plugins/zentao/e2e/bug_commits_test.go
@@ -37,7 +37,9 @@ func TestZentaoBugCommitsDataFlow(t *testing.T) {
                Options: &tasks.ZentaoOptions{
                        ConnectionId: 1,
                        ProjectId:    0,
-                       ProductId:    22,
+               },
+               ProductList: map[int64]string{
+                       22: "",
                },
        }
 
diff --git a/backend/plugins/zentao/e2e/bug_test.go 
b/backend/plugins/zentao/e2e/bug_test.go
index bb0380404..fc123991c 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) {
                Options: &tasks.ZentaoOptions{
                        ConnectionId: 1,
                        ProjectId:    1,
-                       ProductId:    3,
                        ScopeConfigs: &tasks.ZentaoScopeConfigs{
                                TypeMappings: map[string]string{
                                        "codeerror": "CODE_ERROR",
@@ -47,6 +46,14 @@ func TestZentaoBugDataFlow(t *testing.T) {
                                },
                        },
                },
+               ProductList: map[int64]string{
+                       3: "",
+               },
+               FromBugList: map[int]bool{
+                       1: true,
+                       2: true,
+                       4: true,
+               },
        }
 
        // import raw data table
diff --git a/backend/plugins/zentao/e2e/execution_test.go 
b/backend/plugins/zentao/e2e/execution_test.go
index 464e3d1ee..dc1e8091b 100644
--- a/backend/plugins/zentao/e2e/execution_test.go
+++ b/backend/plugins/zentao/e2e/execution_test.go
@@ -18,13 +18,14 @@ limitations under the License.
 package e2e
 
 import (
+       "testing"
+
        "github.com/apache/incubator-devlake/core/models/common"
        "github.com/apache/incubator-devlake/core/models/domainlayer/ticket"
        "github.com/apache/incubator-devlake/helpers/e2ehelper"
        "github.com/apache/incubator-devlake/plugins/zentao/impl"
        "github.com/apache/incubator-devlake/plugins/zentao/models"
        "github.com/apache/incubator-devlake/plugins/zentao/tasks"
-       "testing"
 )
 
 func TestZentaoExecutionDataFlow(t *testing.T) {
@@ -38,6 +39,7 @@ func TestZentaoExecutionDataFlow(t *testing.T) {
                        ProjectId:    1,
                        ProductId:    3,
                },
+               ProductList: map[int64]string{},
        }
 
        // import raw data table
diff --git a/backend/plugins/zentao/e2e/product_test.go 
b/backend/plugins/zentao/e2e/product_test.go
index 676c29199..192198b04 100644
--- a/backend/plugins/zentao/e2e/product_test.go
+++ b/backend/plugins/zentao/e2e/product_test.go
@@ -18,13 +18,14 @@ limitations under the License.
 package e2e
 
 import (
+       "testing"
+
        "github.com/apache/incubator-devlake/core/models/common"
        "github.com/apache/incubator-devlake/core/models/domainlayer/ticket"
        "github.com/apache/incubator-devlake/helpers/e2ehelper"
        "github.com/apache/incubator-devlake/plugins/zentao/impl"
        "github.com/apache/incubator-devlake/plugins/zentao/models"
        "github.com/apache/incubator-devlake/plugins/zentao/tasks"
-       "testing"
 )
 
 func TestZentaoProductDataFlow(t *testing.T) {
@@ -38,6 +39,7 @@ func TestZentaoProductDataFlow(t *testing.T) {
                        ProjectId:    1,
                        ProductId:    3,
                },
+               ProductList: map[int64]string{},
        }
 
        // import raw data table
diff --git a/backend/plugins/zentao/e2e/snapshot_tables/_tool_zentao_bugs.csv 
b/backend/plugins/zentao/e2e/snapshot_tables/_tool_zentao_bugs.csv
index e5d90d77a..cd8ee1898 100644
--- a/backend/plugins/zentao/e2e/snapshot_tables/_tool_zentao_bugs.csv
+++ b/backend/plugins/zentao/e2e/snapshot_tables/_tool_zentao_bugs.csv
@@ -5,9 +5,6 @@ 
connection_id,id,project,product,injection,identify,branch,module,execution,plan
 
1,2,7,3,0,0,0,9,1,1,2,1,15,0,0,新闻中心页面问题,hh,3,2,codeerror,",windows",",chrome",,,"<p>[步骤]进入新闻中心</p>
 <p>[结果]页面出现乱码</p>
 
<p>[期望]正常显示rew</p>",delay,,,1,1,2022-10-05T04:16:44.000+00:00,,[email protected],7,测试甲,2012-06-05T02:57:11.000+00:00,主干,0,,2022-10-05T04:19:22.000+00:00,2022-10-06,0,,,,0,,0,,0,0,0,0,,,,,,,1,1,2022-10-05T04:19:22.000+00:00,0,2,3,0,过期Bug,normal,http://iwater.red:8000/api.php/v1/products/1/bugs?limit=100&page=1,,CODE_ERROR
-1,3,7,3,0,0,0,10,1,0,3,2,6,0,0,成果展示页面问题,,3,1,codeerror,,,,,"<p>[步骤]进入成果展示&nbsp;&nbsp;&nbsp;&nbsp;</p>
-<p>[结果]乱码</p>
-<p>[期望]正常显示</p>",active,,,0,0,,,,8,测试乙,2012-06-05T02:58:22.000+00:00,主干,4,开发甲,2012-06-05T02:58:22.000+00:00,,0,,,,0,,0,,0,0,0,0,,,,,,,0,0,2021-04-28T03:09:08.000+00:00,0,1,3,0,激活,normal,http://iwater.red:8000/api.php/v1/products/1/bugs?limit=100&page=1,DONE,CODE_ERROR
 1,4,7,3,0,0,0,11,1,0,4,1,9,0,0,售后服务页面问题,,3,1,codeerror,,,,,"<p>[步骤]进入售后服务</p>
 <p>[结果]乱码</p>
 
<p>[期望]正常显示</p>",resolved,,,1,0,,,,9,测试丙,2012-06-05T03:00:19.000+00:00,主干,9,测试丙,2022-10-05T04:10:08.000+00:00,,1,fixed,主干,2022-10-05T04:09:59.000+00:00,0,,0,,0,0,0,0,,,,,,,0,1,2022-10-05T04:10:08.000+00:00,0,1,3,0,已解决,normal,http://iwater.red:8000/api.php/v1/products/1/bugs?limit=100&page=1,,CODE_ERROR
diff --git 
a/backend/plugins/zentao/e2e/snapshot_tables/_tool_zentao_stories.csv 
b/backend/plugins/zentao/e2e/snapshot_tables/_tool_zentao_stories.csv
index a66f884d9..433218261 100644
--- a/backend/plugins/zentao/e2e/snapshot_tables/_tool_zentao_stories.csv
+++ b/backend/plugins/zentao/e2e/snapshot_tables/_tool_zentao_stories.csv
@@ -1,8 +1,6 @@
 
connection_id,id,product,branch,version,order_in,vision,parent,module,plan,source,source_note,from_bug,feedback,title,keywords,type,category,pri,estimate,status,sub_status,color,stage,lib,from_story,from_version,opened_by_id,opened_by_name,opened_date,assigned_to_id,assigned_to_name,assigned_date,approved_date,last_edited_id,last_edited_date,changed_date,reviewed_by_id,reviewed_date,closed_id,closed_date,closed_reason,activated_date,to_bug,child_stories,link_stories,link_requirements,dup
 [...]
 
1,1,3,0,1,0,rnd,0,1,1,po,,0,0,首页设计和开发,,story,feature,1,1,active,,,developing,0,0,1,2,产品经理,2012-06-05T02:09:49.000+00:00,2,产品经理,,,2,2012-06-05T02:25:19.000+00:00,,0,2012-06-04T16:00:00.000+00:00,0,,,,0,,,,0,0,,,0,0,1,1.0版本
 ,http://iwater.red:8000/api.php/v1/products/1/stories?limit=100&page=1,,REQUIRE
-1,2,3,0,1,0,rnd,0,2,1,po,,0,0,新闻中心的设计和开发。,,story,feature,1,1,active,,,projected,0,0,1,2,产品经理,2012-06-05T02:16:37.000+00:00,2,产品经理,2012-06-05T02:16:37.000+00:00,,2,2012-06-05T02:25:33.000+00:00,,0,2012-06-04T16:00:00.000+00:00,0,,,,0,,,,0,0,,,0,0,1,1.0版本
 ,http://iwater.red:8000/api.php/v1/products/1/stories?limit=100&page=1,,REQUIRE
 
1,3,3,0,2,0,rnd,0,3,1,po,,0,0,成果展示的设计和开发,,story,feature,1,0,active,,,developing,0,0,1,2,产品经理,2012-06-05T02:18:10.000+00:00,2,产品经理,2012-06-05T02:18:10.000+00:00,,2,2012-06-05T02:25:38.000+00:00,,0,2012-06-04T16:00:00.000+00:00,0,,,,0,,,,0,0,,,0,0,1,1.0版本
 ,http://iwater.red:8000/api.php/v1/products/1/stories?limit=100&page=1,,REQUIRE
 
1,4,3,0,1,0,rnd,0,4,1,po,,0,0,售后服务的设计和开发,,story,feature,1,1,active,,,developed,0,0,1,2,产品经理,2012-06-05T02:20:16.000+00:00,2,产品经理,2012-06-05T02:20:16.000+00:00,,2,2012-06-05T02:25:42.000+00:00,,0,2012-06-04T16:00:00.000+00:00,0,,,,0,,,,0,0,,,0,0,1,1.0版本
 ,http://iwater.red:8000/api.php/v1/products/1/stories?limit=100&page=1,,REQUIRE
-1,5,3,0,1,0,rnd,0,5,1,po,,0,0,诚聘英才的设计和开发,,story,feature,1,1,reviewing,,,planned,0,0,1,2,产品经理,2012-06-05T02:21:39.000+00:00,2,产品经理,2012-06-05T02:21:39.000+00:00,,0,,,0,,0,,,,0,,,,0,0,,,0,0,1,1.0版本
 ,http://iwater.red:8000/api.php/v1/products/1/stories?limit=100&page=1,,REQUIRE
 
1,6,3,0,1,0,rnd,0,6,1,po,,0,0,合作洽谈的设计和开发,,story,feature,1,1,reviewing,,,planned,0,0,1,2,产品经理,2012-06-05T02:23:11.000+00:00,2,产品经理,2012-06-05T02:23:11.000+00:00,,0,,,0,,0,,,,0,,,,0,0,,,0,0,1,1.0版本
 ,http://iwater.red:8000/api.php/v1/products/1/stories?limit=100&page=1,,REQUIRE
 
1,7,3,0,1,0,rnd,0,7,1,po,,0,0,关于我们的设计和开发,,story,feature,1,1,reviewing,,,planned,0,0,1,2,产品经理,2012-06-05T02:24:19.000+00:00,2,产品经理,2012-06-05T02:24:19.000+00:00,,0,,,0,,0,,,,0,,,,0,0,,,0,0,1,1.0版本
 ,http://iwater.red:8000/api.php/v1/products/1/stories?limit=100&page=1,,REQUIRE
diff --git a/backend/plugins/zentao/e2e/snapshot_tables/board_issues_bug.csv 
b/backend/plugins/zentao/e2e/snapshot_tables/board_issues_bug.csv
index af59f0f72..b06a99454 100644
--- a/backend/plugins/zentao/e2e/snapshot_tables/board_issues_bug.csv
+++ b/backend/plugins/zentao/e2e/snapshot_tables/board_issues_bug.csv
@@ -1,5 +1,4 @@
 board_id,issue_id
 zentao:ZentaoProduct:1:3,zentao:ZentaoBug:1:1
 zentao:ZentaoProduct:1:3,zentao:ZentaoBug:1:2
-zentao:ZentaoProduct:1:3,zentao:ZentaoBug:1:3
 zentao:ZentaoProduct:1:3,zentao:ZentaoBug:1:4
diff --git a/backend/plugins/zentao/e2e/snapshot_tables/board_issues_story.csv 
b/backend/plugins/zentao/e2e/snapshot_tables/board_issues_story.csv
index 2bfddefa4..38035dda7 100644
--- a/backend/plugins/zentao/e2e/snapshot_tables/board_issues_story.csv
+++ b/backend/plugins/zentao/e2e/snapshot_tables/board_issues_story.csv
@@ -1,8 +1,6 @@
 board_id,issue_id
 zentao:ZentaoProduct:1:3,zentao:ZentaoStory:1:1
-zentao:ZentaoProduct:1:3,zentao:ZentaoStory:1:2
 zentao:ZentaoProduct:1:3,zentao:ZentaoStory:1:3
 zentao:ZentaoProduct:1:3,zentao:ZentaoStory:1:4
-zentao:ZentaoProduct:1:3,zentao:ZentaoStory:1:5
 zentao:ZentaoProduct:1:3,zentao:ZentaoStory:1:6
 zentao:ZentaoProduct:1:3,zentao:ZentaoStory:1:7
diff --git a/backend/plugins/zentao/e2e/snapshot_tables/bug_issue_assignees.csv 
b/backend/plugins/zentao/e2e/snapshot_tables/bug_issue_assignees.csv
index 2f2e3ade7..ee6029667 100644
--- a/backend/plugins/zentao/e2e/snapshot_tables/bug_issue_assignees.csv
+++ b/backend/plugins/zentao/e2e/snapshot_tables/bug_issue_assignees.csv
@@ -1,5 +1,4 @@
 issue_id,assignee_id,assignee_name
 zentao:ZentaoBug:1:1,4,开发甲
 zentao:ZentaoBug:1:2,0,
-zentao:ZentaoBug:1:3,4,开发甲
 zentao:ZentaoBug:1:4,9,测试丙
diff --git a/backend/plugins/zentao/e2e/snapshot_tables/issues_bug.csv 
b/backend/plugins/zentao/e2e/snapshot_tables/issues_bug.csv
index a6ec75001..f80cbf28c 100644
--- a/backend/plugins/zentao/e2e/snapshot_tables/issues_bug.csv
+++ b/backend/plugins/zentao/e2e/snapshot_tables/issues_bug.csv
@@ -1,5 +1,4 @@
 
id,url,icon_url,issue_key,title,description,epic_key,type,original_type,status,original_status,story_point,resolution_date,created_date,updated_date,lead_time_minutes,parent_issue_id,priority,original_estimate_minutes,time_spent_minutes,time_remaining_minutes,creator_id,creator_name,assignee_id,assignee_name,severity,component
 
zentao:ZentaoBug:1:1,http://iwater.red:8000/api.php/v1/products/1/bugs?limit=100&page=1,,1,首页页面问题,,,BUG,codeerror,DONE,active,0,,2012-06-05T02:56:11.000+00:00,2021-04-28T03:09:08.000+00:00,0,zentao:ZentaoStory:1:1,VeryHigh,0,0,0,7,测试甲,4,开发甲,,
 
zentao:ZentaoBug:1:2,http://iwater.red:8000/api.php/v1/products/1/bugs?limit=100&page=1,,2,新闻中心页面问题,,,BUG,codeerror,,delay,0,,2012-06-05T02:57:11.000+00:00,2022-10-05T04:19:22.000+00:00,0,zentao:ZentaoStory:1:2,High,0,0,0,7,测试甲,0,,,
-zentao:ZentaoBug:1:3,http://iwater.red:8000/api.php/v1/products/1/bugs?limit=100&page=1,,3,成果展示页面问题,,,BUG,codeerror,DONE,active,0,,2012-06-05T02:58:22.000+00:00,2021-04-28T03:09:08.000+00:00,0,zentao:ZentaoStory:1:3,VeryHigh,0,0,0,8,测试乙,4,开发甲,,
 
zentao:ZentaoBug:1:4,http://iwater.red:8000/api.php/v1/products/1/bugs?limit=100&page=1,,4,售后服务页面问题,,,BUG,codeerror,,resolved,0,,2012-06-05T03:00:19.000+00:00,2022-10-05T04:10:08.000+00:00,0,zentao:ZentaoStory:1:4,VeryHigh,0,0,0,9,测试丙,9,测试丙,,
diff --git a/backend/plugins/zentao/e2e/snapshot_tables/issues_story.csv 
b/backend/plugins/zentao/e2e/snapshot_tables/issues_story.csv
index b922055dc..c838aa818 100644
--- a/backend/plugins/zentao/e2e/snapshot_tables/issues_story.csv
+++ b/backend/plugins/zentao/e2e/snapshot_tables/issues_story.csv
@@ -1,8 +1,6 @@
 
id,url,icon_url,issue_key,title,description,epic_key,type,original_type,status,original_status,story_point,resolution_date,created_date,updated_date,lead_time_minutes,parent_issue_id,priority,original_estimate_minutes,time_spent_minutes,time_remaining_minutes,creator_id,creator_name,assignee_id,assignee_name,severity,component
 
zentao:ZentaoStory:1:1,http://iwater.red:8000/api.php/v1/products/1/stories?limit=100&page=1,,1,首页设计和开发,,,REQUIREMENT,story.feature,,active-developing,0,,2012-06-05T02:09:49.000+00:00,2012-06-05T02:25:19.000+00:00,0,zentao:ZentaoStory:1:0,VeryHigh,0,0,0,2,产品经理,2,产品经理,,
-zentao:ZentaoStory:1:2,http://iwater.red:8000/api.php/v1/products/1/stories?limit=100&page=1,,2,新闻中心的设计和开发。,,,REQUIREMENT,story.feature,,active-projected,0,,2012-06-05T02:16:37.000+00:00,2012-06-05T02:25:33.000+00:00,0,zentao:ZentaoStory:1:0,VeryHigh,0,0,0,2,产品经理,2,产品经理,,
 
zentao:ZentaoStory:1:3,http://iwater.red:8000/api.php/v1/products/1/stories?limit=100&page=1,,3,成果展示的设计和开发,,,REQUIREMENT,story.feature,,active-developing,0,,2012-06-05T02:18:10.000+00:00,2012-06-05T02:25:38.000+00:00,0,zentao:ZentaoStory:1:0,VeryHigh,0,0,0,2,产品经理,2,产品经理,,
 
zentao:ZentaoStory:1:4,http://iwater.red:8000/api.php/v1/products/1/stories?limit=100&page=1,,4,售后服务的设计和开发,,,REQUIREMENT,story.feature,,active-developed,0,,2012-06-05T02:20:16.000+00:00,2012-06-05T02:25:42.000+00:00,0,zentao:ZentaoStory:1:0,VeryHigh,0,0,0,2,产品经理,2,产品经理,,
-zentao:ZentaoStory:1:5,http://iwater.red:8000/api.php/v1/products/1/stories?limit=100&page=1,,5,诚聘英才的设计和开发,,,REQUIREMENT,story.feature,,reviewing-planned,0,,2012-06-05T02:21:39.000+00:00,,0,zentao:ZentaoStory:1:0,VeryHigh,0,0,0,2,产品经理,2,产品经理,,
 
zentao:ZentaoStory:1:6,http://iwater.red:8000/api.php/v1/products/1/stories?limit=100&page=1,,6,合作洽谈的设计和开发,,,REQUIREMENT,story.feature,,reviewing-planned,0,,2012-06-05T02:23:11.000+00:00,,0,zentao:ZentaoStory:1:0,VeryHigh,0,0,0,2,产品经理,2,产品经理,,
 
zentao:ZentaoStory:1:7,http://iwater.red:8000/api.php/v1/products/1/stories?limit=100&page=1,,7,关于我们的设计和开发,,,REQUIREMENT,story.feature,,reviewing-planned,0,,2012-06-05T02:24:19.000+00:00,,0,zentao:ZentaoStory:1:0,VeryHigh,0,0,0,2,产品经理,2,产品经理,,
diff --git 
a/backend/plugins/zentao/e2e/snapshot_tables/story_issue_assignees.csv 
b/backend/plugins/zentao/e2e/snapshot_tables/story_issue_assignees.csv
index ba8ab516a..0e43ee566 100644
--- a/backend/plugins/zentao/e2e/snapshot_tables/story_issue_assignees.csv
+++ b/backend/plugins/zentao/e2e/snapshot_tables/story_issue_assignees.csv
@@ -1,8 +1,6 @@
 issue_id,assignee_id,assignee_name
 zentao:ZentaoStory:1:1,2,产品经理
-zentao:ZentaoStory:1:2,2,产品经理
 zentao:ZentaoStory:1:3,2,产品经理
 zentao:ZentaoStory:1:4,2,产品经理
-zentao:ZentaoStory:1:5,2,产品经理
 zentao:ZentaoStory:1:6,2,产品经理
 zentao:ZentaoStory:1:7,2,产品经理
diff --git a/backend/plugins/zentao/e2e/story_commits_test.go 
b/backend/plugins/zentao/e2e/story_commits_test.go
index 64fa9bdf5..02cbc9841 100644
--- a/backend/plugins/zentao/e2e/story_commits_test.go
+++ b/backend/plugins/zentao/e2e/story_commits_test.go
@@ -37,7 +37,9 @@ func TestZentaoStoryCommitsDataFlow(t *testing.T) {
                Options: &tasks.ZentaoOptions{
                        ConnectionId: 1,
                        ProjectId:    0,
-                       ProductId:    31,
+               },
+               ProductList: map[int64]string{
+                       31: "",
                },
        }
 
diff --git a/backend/plugins/zentao/e2e/story_test.go 
b/backend/plugins/zentao/e2e/story_test.go
index 214245b21..a5c0ec706 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) {
                Options: &tasks.ZentaoOptions{
                        ConnectionId: 1,
                        ProjectId:    1,
-                       ProductId:    3,
                        ScopeConfigs: &tasks.ZentaoScopeConfigs{
                                TypeMappings: map[string]string{
                                        "story.feature": "REQUIRE",
@@ -47,6 +46,16 @@ func TestZentaoStoryDataFlow(t *testing.T) {
                                },
                        },
                },
+               ProductList: map[int64]string{
+                       3: "",
+               },
+               StoryList: map[int64]int64{
+                       1: 1,
+                       3: 3,
+                       4: 4,
+                       6: 6,
+                       7: 7,
+               },
        }
 
        // import raw data table
diff --git a/backend/plugins/zentao/e2e/task_test.go 
b/backend/plugins/zentao/e2e/task_test.go
index 085b514bd..7e890494e 100644
--- a/backend/plugins/zentao/e2e/task_test.go
+++ b/backend/plugins/zentao/e2e/task_test.go
@@ -47,6 +47,8 @@ func TestZentaoTaskDataFlow(t *testing.T) {
                                },
                        },
                },
+               StoryList:   map[int64]int64{},
+               FromBugList: map[int]bool{},
        }
 
        // import raw data table
diff --git a/backend/plugins/zentao/impl/impl.go 
b/backend/plugins/zentao/impl/impl.go
index 6370d5264..c6e9e487a 100644
--- a/backend/plugins/zentao/impl/impl.go
+++ b/backend/plugins/zentao/impl/impl.go
@@ -100,16 +100,37 @@ func (p Zentao) ScopeConfig() dal.Tabler {
 
 func (p Zentao) SubTaskMetas() []plugin.SubTaskMeta {
        return []plugin.SubTaskMeta{
-               tasks.ConvertProductMeta,
+               //tasks.ConvertProductMeta,
                tasks.ConvertProjectMeta,
 
                tasks.DBGetChangelogMeta,
                tasks.ConvertChangelogMeta,
 
+               // both
+               tasks.CollectAccountMeta,
+               tasks.ExtractAccountMeta,
+               tasks.ConvertAccountMeta,
+
+               tasks.CollectDepartmentMeta,
+               tasks.ExtractDepartmentMeta,
+               tasks.ConvertDepartmentMeta,
+
+               // project
                tasks.CollectExecutionMeta,
                tasks.ExtractExecutionMeta,
                tasks.ConvertExecutionMeta,
 
+               tasks.CollectTaskMeta,
+               tasks.ExtractTaskMeta,
+               tasks.ConvertTaskMeta,
+
+               tasks.CollectTaskCommitsMeta,
+               tasks.ExtractTaskCommitsMeta,
+               tasks.CollectTaskRepoCommitsMeta,
+               tasks.ExtractTaskRepoCommitsMeta,
+               tasks.ConvertTaskRepoCommitsMeta,
+
+               // product
                tasks.CollectStoryMeta,
                tasks.ExtractStoryMeta,
                tasks.ConvertStoryMeta,
@@ -118,35 +139,17 @@ func (p Zentao) SubTaskMetas() []plugin.SubTaskMeta {
                tasks.ExtractBugMeta,
                tasks.ConvertBugMeta,
 
-               tasks.CollectTaskMeta,
-               tasks.ExtractTaskMeta,
-               tasks.ConvertTaskMeta,
-
-               tasks.CollectAccountMeta,
-               tasks.ExtractAccountMeta,
-               tasks.ConvertAccountMeta,
-
-               tasks.CollectDepartmentMeta,
-               tasks.ExtractDepartmentMeta,
-               tasks.ConvertDepartmentMeta,
-
-               tasks.CollectBugCommitsMeta,
-               tasks.ExtractBugCommitsMeta,
-               tasks.CollectBugRepoCommitsMeta,
-               tasks.ExtractBugRepoCommitsMeta,
-               tasks.ConvertBugRepoCommitsMeta,
-
                tasks.CollectStoryCommitsMeta,
                tasks.ExtractStoryCommitsMeta,
                tasks.CollectStoryRepoCommitsMeta,
                tasks.ExtractStoryRepoCommitsMeta,
                tasks.ConvertStoryRepoCommitsMeta,
 
-               tasks.CollectTaskCommitsMeta,
-               tasks.ExtractTaskCommitsMeta,
-               tasks.CollectTaskRepoCommitsMeta,
-               tasks.ExtractTaskRepoCommitsMeta,
-               tasks.ConvertTaskRepoCommitsMeta,
+               tasks.CollectBugCommitsMeta,
+               tasks.ExtractBugCommitsMeta,
+               tasks.CollectBugRepoCommitsMeta,
+               tasks.ExtractBugRepoCommitsMeta,
+               tasks.ConvertBugRepoCommitsMeta,
        }
 }
 
@@ -184,8 +187,11 @@ func (p Zentao) PrepareTaskData(taskCtx 
plugin.TaskContext, options map[string]i
        }
 
        data := &tasks.ZentaoTaskData{
-               Options:   op,
-               ApiClient: apiClient,
+               Options:     op,
+               ApiClient:   apiClient,
+               ProductList: map[int64]string{},
+               StoryList:   map[int64]int64{},
+               FromBugList: map[int]bool{},
        }
 
        if connection.DbUrl != "" {
@@ -241,21 +247,32 @@ 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,
-                       "DELETE": api.DeleteProductScope,
+               /*
+                       "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,
+                               "DELETE": api.DeleteProductScope,
+                       },
+                       "connections/:connectionId/scopes/project/:scopeId": {
+                               "GET":    api.GetProjectScope,
+                               "PATCH":  api.UpdateProjectScope,
+                               "DELETE": api.DeleteProjectScope,
+                       },
+               */
+               "connections/:connectionId/scopes": {
+                       "PUT": api.PutScope,
+                       "GET": api.GetScopeList,
                },
-               "connections/:connectionId/scopes/project/:scopeId": {
-                       "GET":    api.GetProjectScope,
-                       "PATCH":  api.UpdateProjectScope,
-                       "DELETE": api.DeleteProjectScope,
+               "connections/:connectionId/scopes/:scopeId": {
+                       "GET":    api.GetScope,
+                       "PATCH":  api.UpdateScope,
+                       "DELETE": api.DeleteScope,
                },
                "connections/:connectionId/scope-configs": {
                        "POST": api.CreateScopeConfig,
diff --git a/backend/plugins/zentao/models/connection.go 
b/backend/plugins/zentao/models/connection.go
index 6e8ed4771..09c28cef6 100644
--- a/backend/plugins/zentao/models/connection.go
+++ b/backend/plugins/zentao/models/connection.go
@@ -45,7 +45,7 @@ func (connection ZentaoConn) PrepareApiClient(apiClient 
apihelperabstract.ApiCli
        tokenResBody := &ApiAccessTokenResponse{}
        err = helper.UnmarshalResponse(tokenRes, tokenResBody)
        if err != nil {
-               return err
+               return errors.HttpStatus(http.StatusBadRequest).Wrap(err, 
"failed UnmarshalResponse for tokenResBody")
        }
        if tokenResBody.Token == "" {
                return errors.HttpStatus(http.StatusBadRequest).New("failed to 
request access token")
diff --git a/backend/plugins/zentao/tasks/bug_collector.go 
b/backend/plugins/zentao/tasks/bug_collector.go
index 7692cb596..370222575 100644
--- a/backend/plugins/zentao/tasks/bug_collector.go
+++ b/backend/plugins/zentao/tasks/bug_collector.go
@@ -33,6 +33,10 @@ const RAW_BUG_TABLE = "zentao_api_bugs"
 var _ plugin.SubTaskEntryPoint = CollectBug
 
 func CollectBug(taskCtx plugin.SubTaskContext) errors.Error {
+       return RangeProductOneByOne(taskCtx, CollectBugForOneProduct)
+}
+
+func CollectBugForOneProduct(taskCtx plugin.SubTaskContext) errors.Error {
        data := taskCtx.GetData().(*ZentaoTaskData)
 
        // this collect only work for product
diff --git a/backend/plugins/zentao/tasks/bug_commits_collector.go 
b/backend/plugins/zentao/tasks/bug_commits_collector.go
index 99ffd307f..03fabf42c 100644
--- a/backend/plugins/zentao/tasks/bug_commits_collector.go
+++ b/backend/plugins/zentao/tasks/bug_commits_collector.go
@@ -42,6 +42,10 @@ var CollectBugCommitsMeta = plugin.SubTaskMeta{
 }
 
 func CollectBugCommits(taskCtx plugin.SubTaskContext) errors.Error {
+       return RangeProductOneByOne(taskCtx, CollectBugCommitsForOneProduct)
+}
+
+func CollectBugCommitsForOneProduct(taskCtx plugin.SubTaskContext) 
errors.Error {
        db := taskCtx.GetDal()
        data := taskCtx.GetData().(*ZentaoTaskData)
 
diff --git a/backend/plugins/zentao/tasks/bug_commits_extractor.go 
b/backend/plugins/zentao/tasks/bug_commits_extractor.go
index 8492810a2..da4ccb564 100644
--- a/backend/plugins/zentao/tasks/bug_commits_extractor.go
+++ b/backend/plugins/zentao/tasks/bug_commits_extractor.go
@@ -39,6 +39,10 @@ var ExtractBugCommitsMeta = plugin.SubTaskMeta{
 }
 
 func ExtractBugCommits(taskCtx plugin.SubTaskContext) errors.Error {
+       return RangeProductOneByOne(taskCtx, ExtractBugCommitsForOneProduct)
+}
+
+func ExtractBugCommitsForOneProduct(taskCtx plugin.SubTaskContext) 
errors.Error {
        data := taskCtx.GetData().(*ZentaoTaskData)
 
        // this Extract only work for product
diff --git a/backend/plugins/zentao/tasks/bug_convertor.go 
b/backend/plugins/zentao/tasks/bug_convertor.go
index 51fafc467..1a085c4be 100644
--- a/backend/plugins/zentao/tasks/bug_convertor.go
+++ b/backend/plugins/zentao/tasks/bug_convertor.go
@@ -42,6 +42,10 @@ var ConvertBugMeta = plugin.SubTaskMeta{
 }
 
 func ConvertBug(taskCtx plugin.SubTaskContext) errors.Error {
+       return RangeProductOneByOne(taskCtx, ConvertBugForOneProduct)
+}
+
+func ConvertBugForOneProduct(taskCtx plugin.SubTaskContext) errors.Error {
        data := taskCtx.GetData().(*ZentaoTaskData)
        db := taskCtx.GetDal()
        bugIdGen := didgen.NewDomainIdGenerator(&models.ZentaoBug{})
diff --git a/backend/plugins/zentao/tasks/bug_extractor.go 
b/backend/plugins/zentao/tasks/bug_extractor.go
index 3f3396d6e..010ab743e 100644
--- a/backend/plugins/zentao/tasks/bug_extractor.go
+++ b/backend/plugins/zentao/tasks/bug_extractor.go
@@ -38,6 +38,10 @@ var ExtractBugMeta = plugin.SubTaskMeta{
 }
 
 func ExtractBug(taskCtx plugin.SubTaskContext) errors.Error {
+       return RangeProductOneByOne(taskCtx, ExtractBugForOneProduct)
+}
+
+func ExtractBugForOneProduct(taskCtx plugin.SubTaskContext) errors.Error {
        data := taskCtx.GetData().(*ZentaoTaskData)
 
        // this Extract only work for product
@@ -64,6 +68,14 @@ func ExtractBug(taskCtx plugin.SubTaskContext) errors.Error {
                        if err != nil {
                                return nil, errors.Default.WrapRaw(err)
                        }
+
+                       // project scope need filter
+                       if data.Options.ProjectId != 0 {
+                               if init, ok := data.FromBugList[int(res.ID)]; 
!ok || !init {
+                                       return nil, nil
+                               }
+                       }
+
                        bug := &models.ZentaoBug{
                                ConnectionId:   data.Options.ConnectionId,
                                ID:             res.ID,
diff --git a/backend/plugins/zentao/tasks/bug_repo_commits_collector.go 
b/backend/plugins/zentao/tasks/bug_repo_commits_collector.go
index c9f28ae02..607e359fa 100644
--- a/backend/plugins/zentao/tasks/bug_repo_commits_collector.go
+++ b/backend/plugins/zentao/tasks/bug_repo_commits_collector.go
@@ -42,6 +42,10 @@ var CollectBugRepoCommitsMeta = plugin.SubTaskMeta{
 }
 
 func CollectBugRepoCommits(taskCtx plugin.SubTaskContext) errors.Error {
+       return RangeProductOneByOne(taskCtx, CollectBugRepoCommitsForOneProduct)
+}
+
+func CollectBugRepoCommitsForOneProduct(taskCtx plugin.SubTaskContext) 
errors.Error {
        db := taskCtx.GetDal()
        data := taskCtx.GetData().(*ZentaoTaskData)
 
diff --git a/backend/plugins/zentao/tasks/bug_repo_commits_convertor.go 
b/backend/plugins/zentao/tasks/bug_repo_commits_convertor.go
index f425f2c76..ddc5f522a 100644
--- a/backend/plugins/zentao/tasks/bug_repo_commits_convertor.go
+++ b/backend/plugins/zentao/tasks/bug_repo_commits_convertor.go
@@ -40,6 +40,10 @@ var ConvertBugRepoCommitsMeta = plugin.SubTaskMeta{
 }
 
 func ConvertBugRepoCommits(taskCtx plugin.SubTaskContext) errors.Error {
+       return RangeProductOneByOne(taskCtx, ConvertBugRepoCommitsForOneProduct)
+}
+
+func ConvertBugRepoCommitsForOneProduct(taskCtx plugin.SubTaskContext) 
errors.Error {
        data := taskCtx.GetData().(*ZentaoTaskData)
        db := taskCtx.GetDal()
 
diff --git a/backend/plugins/zentao/tasks/bug_repo_commits_extractor.go 
b/backend/plugins/zentao/tasks/bug_repo_commits_extractor.go
index 292416bd4..6bc8dfd24 100644
--- a/backend/plugins/zentao/tasks/bug_repo_commits_extractor.go
+++ b/backend/plugins/zentao/tasks/bug_repo_commits_extractor.go
@@ -38,6 +38,10 @@ var ExtractBugRepoCommitsMeta = plugin.SubTaskMeta{
 }
 
 func ExtractBugRepoCommits(taskCtx plugin.SubTaskContext) errors.Error {
+       return RangeProductOneByOne(taskCtx, ExtractBugRepoCommitsForOneProduct)
+}
+
+func ExtractBugRepoCommitsForOneProduct(taskCtx plugin.SubTaskContext) 
errors.Error {
        data := taskCtx.GetData().(*ZentaoTaskData)
 
        // this Extract only work for product
diff --git a/backend/plugins/zentao/tasks/execution_extractor.go 
b/backend/plugins/zentao/tasks/execution_extractor.go
index a891866d7..f03970865 100644
--- a/backend/plugins/zentao/tasks/execution_extractor.go
+++ b/backend/plugins/zentao/tasks/execution_extractor.go
@@ -60,6 +60,12 @@ func ExtractExecutions(taskCtx plugin.SubTaskContext) 
errors.Error {
                        if err != nil {
                                return nil, errors.Default.WrapRaw(err)
                        }
+
+                       // append product to taskdata
+                       for _, product := range res.Products {
+                               data.ProductList[product.ID] = product.Name
+                       }
+
                        execution := &models.ZentaoExecution{
                                ConnectionId:   data.Options.ConnectionId,
                                Id:             res.ID,
diff --git a/backend/plugins/zentao/tasks/product_convertor.go 
b/backend/plugins/zentao/tasks/product_convertor.go
index fc3a645eb..3e76241a6 100644
--- a/backend/plugins/zentao/tasks/product_convertor.go
+++ b/backend/plugins/zentao/tasks/product_convertor.go
@@ -70,7 +70,7 @@ func ConvertProducts(taskCtx plugin.SubTaskContext) 
errors.Error {
                Convert: func(inputRow interface{}) ([]interface{}, 
errors.Error) {
                        toolProduct := inputRow.(*models.ZentaoProduct)
 
-                       data.ProductName = toolProduct.Name
+                       data.ProductList[toolProduct.Id] = toolProduct.Name
 
                        domainBoard := &ticket.Board{
                                DomainEntity: domainlayer.DomainEntity{
diff --git a/backend/plugins/zentao/tasks/shared.go 
b/backend/plugins/zentao/tasks/shared.go
index 3c16b347e..2a276d627 100644
--- a/backend/plugins/zentao/tasks/shared.go
+++ b/backend/plugins/zentao/tasks/shared.go
@@ -24,6 +24,7 @@ import (
        "strings"
 
        "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"
 )
@@ -177,3 +178,19 @@ func ignoreHTTPStatus404(res *http.Response) errors.Error {
        }
        return nil
 }
+
+func RangeProductOneByOne(taskCtx plugin.SubTaskContext, callback func(taskCtx 
plugin.SubTaskContext) errors.Error) errors.Error {
+       data := taskCtx.GetData().(*ZentaoTaskData)
+
+       for id, name := range data.ProductList {
+               data.Options.ProductId = id
+               data.ProductName = name
+
+               err := callback(taskCtx)
+               if err != nil {
+                       return err
+               }
+       }
+
+       return nil
+}
diff --git a/backend/plugins/zentao/tasks/story_collector.go 
b/backend/plugins/zentao/tasks/story_collector.go
index f11918ad8..17fadd05c 100644
--- a/backend/plugins/zentao/tasks/story_collector.go
+++ b/backend/plugins/zentao/tasks/story_collector.go
@@ -33,6 +33,10 @@ const RAW_STORY_TABLE = "zentao_api_stories"
 var _ plugin.SubTaskEntryPoint = CollectStory
 
 func CollectStory(taskCtx plugin.SubTaskContext) errors.Error {
+       return RangeProductOneByOne(taskCtx, CollectStoryForOneProduct)
+}
+
+func CollectStoryForOneProduct(taskCtx plugin.SubTaskContext) errors.Error {
        data := taskCtx.GetData().(*ZentaoTaskData)
 
        // this collect only work for product
diff --git a/backend/plugins/zentao/tasks/story_commits_collector.go 
b/backend/plugins/zentao/tasks/story_commits_collector.go
index 0dca49768..e8b654366 100644
--- a/backend/plugins/zentao/tasks/story_commits_collector.go
+++ b/backend/plugins/zentao/tasks/story_commits_collector.go
@@ -42,6 +42,10 @@ var CollectStoryCommitsMeta = plugin.SubTaskMeta{
 }
 
 func CollectStoryCommits(taskCtx plugin.SubTaskContext) errors.Error {
+       return RangeProductOneByOne(taskCtx, CollectStoryCommitsForOneProduct)
+}
+
+func CollectStoryCommitsForOneProduct(taskCtx plugin.SubTaskContext) 
errors.Error {
        db := taskCtx.GetDal()
        data := taskCtx.GetData().(*ZentaoTaskData)
 
diff --git a/backend/plugins/zentao/tasks/story_commits_extractor.go 
b/backend/plugins/zentao/tasks/story_commits_extractor.go
index 0e119c3e6..d16d1efb3 100644
--- a/backend/plugins/zentao/tasks/story_commits_extractor.go
+++ b/backend/plugins/zentao/tasks/story_commits_extractor.go
@@ -39,6 +39,10 @@ var ExtractStoryCommitsMeta = plugin.SubTaskMeta{
 }
 
 func ExtractStoryCommits(taskCtx plugin.SubTaskContext) errors.Error {
+       return RangeProductOneByOne(taskCtx, ExtractStoryCommitsForOneProduct)
+}
+
+func ExtractStoryCommitsForOneProduct(taskCtx plugin.SubTaskContext) 
errors.Error {
        data := taskCtx.GetData().(*ZentaoTaskData)
 
        // this Extract only work for product
diff --git a/backend/plugins/zentao/tasks/story_convertor.go 
b/backend/plugins/zentao/tasks/story_convertor.go
index cf1097e1b..3d9a214c9 100644
--- a/backend/plugins/zentao/tasks/story_convertor.go
+++ b/backend/plugins/zentao/tasks/story_convertor.go
@@ -42,6 +42,10 @@ var ConvertStoryMeta = plugin.SubTaskMeta{
 }
 
 func ConvertStory(taskCtx plugin.SubTaskContext) errors.Error {
+       return RangeProductOneByOne(taskCtx, ConvertStoryForOneProduct)
+}
+
+func ConvertStoryForOneProduct(taskCtx plugin.SubTaskContext) errors.Error {
        data := taskCtx.GetData().(*ZentaoTaskData)
        db := taskCtx.GetDal()
        storyIdGen := didgen.NewDomainIdGenerator(&models.ZentaoStory{})
diff --git a/backend/plugins/zentao/tasks/story_extractor.go 
b/backend/plugins/zentao/tasks/story_extractor.go
index b8ec78ae9..dd8c75622 100644
--- a/backend/plugins/zentao/tasks/story_extractor.go
+++ b/backend/plugins/zentao/tasks/story_extractor.go
@@ -38,6 +38,10 @@ var ExtractStoryMeta = plugin.SubTaskMeta{
 }
 
 func ExtractStory(taskCtx plugin.SubTaskContext) errors.Error {
+       return RangeProductOneByOne(taskCtx, ExtractStoryForOneProduct)
+}
+
+func ExtractStoryForOneProduct(taskCtx plugin.SubTaskContext) errors.Error {
        data := taskCtx.GetData().(*ZentaoTaskData)
 
        // this collect only work for product
@@ -64,6 +68,14 @@ func ExtractStory(taskCtx plugin.SubTaskContext) 
errors.Error {
                        if err != nil {
                                return nil, errors.Default.WrapRaw(err)
                        }
+
+                       // project scope need filter
+                       if data.Options.ProjectId != 0 {
+                               if _, ok := data.StoryList[res.ID]; !ok {
+                                       return nil, nil
+                               }
+                       }
+
                        story := &models.ZentaoStory{
                                ConnectionId:     data.Options.ConnectionId,
                                ID:               res.ID,
diff --git a/backend/plugins/zentao/tasks/story_repo_commits_collector.go 
b/backend/plugins/zentao/tasks/story_repo_commits_collector.go
index ddcdbf47a..f28dbb12c 100644
--- a/backend/plugins/zentao/tasks/story_repo_commits_collector.go
+++ b/backend/plugins/zentao/tasks/story_repo_commits_collector.go
@@ -42,6 +42,10 @@ var CollectStoryRepoCommitsMeta = plugin.SubTaskMeta{
 }
 
 func CollectStoryRepoCommits(taskCtx plugin.SubTaskContext) errors.Error {
+       return RangeProductOneByOne(taskCtx, 
CollectStoryRepoCommitsForOneProduct)
+}
+
+func CollectStoryRepoCommitsForOneProduct(taskCtx plugin.SubTaskContext) 
errors.Error {
        db := taskCtx.GetDal()
        data := taskCtx.GetData().(*ZentaoTaskData)
 
diff --git a/backend/plugins/zentao/tasks/story_repo_commits_convertor.go 
b/backend/plugins/zentao/tasks/story_repo_commits_convertor.go
index 612a61929..051a243ed 100644
--- a/backend/plugins/zentao/tasks/story_repo_commits_convertor.go
+++ b/backend/plugins/zentao/tasks/story_repo_commits_convertor.go
@@ -40,6 +40,10 @@ var ConvertStoryRepoCommitsMeta = plugin.SubTaskMeta{
 }
 
 func ConvertStoryRepoCommits(taskCtx plugin.SubTaskContext) errors.Error {
+       return RangeProductOneByOne(taskCtx, 
ConvertStoryRepoCommitsForOneProduct)
+}
+
+func ConvertStoryRepoCommitsForOneProduct(taskCtx plugin.SubTaskContext) 
errors.Error {
        data := taskCtx.GetData().(*ZentaoTaskData)
        db := taskCtx.GetDal()
 
diff --git a/backend/plugins/zentao/tasks/story_repo_commits_extractor.go 
b/backend/plugins/zentao/tasks/story_repo_commits_extractor.go
index 84a6fa294..d2b0efa45 100644
--- a/backend/plugins/zentao/tasks/story_repo_commits_extractor.go
+++ b/backend/plugins/zentao/tasks/story_repo_commits_extractor.go
@@ -38,6 +38,10 @@ var ExtractStoryRepoCommitsMeta = plugin.SubTaskMeta{
 }
 
 func ExtractStoryRepoCommits(taskCtx plugin.SubTaskContext) errors.Error {
+       return RangeProductOneByOne(taskCtx, 
ExtractStoryRepoCommitsForOneProduct)
+}
+
+func ExtractStoryRepoCommitsForOneProduct(taskCtx plugin.SubTaskContext) 
errors.Error {
        data := taskCtx.GetData().(*ZentaoTaskData)
 
        // this Extract only work for product
diff --git a/backend/plugins/zentao/tasks/task_data.go 
b/backend/plugins/zentao/tasks/task_data.go
index d406f4868..bfebb2208 100644
--- a/backend/plugins/zentao/tasks/task_data.go
+++ b/backend/plugins/zentao/tasks/task_data.go
@@ -105,6 +105,9 @@ type ZentaoTaskData struct {
        TimeAfter   *time.Time
        ProjectName string
        ProductName string
+       ProductList map[int64]string // set if it is setting project id, it is 
map[id]name
+       StoryList   map[int64]int64  // set if it is run the task_extractor
+       FromBugList map[int]bool     // set if it is run the task_extracor
        ApiClient   *helper.ApiAsyncClient
 }
 
diff --git a/backend/plugins/zentao/tasks/task_extractor.go 
b/backend/plugins/zentao/tasks/task_extractor.go
index 070327743..28763e2fb 100644
--- a/backend/plugins/zentao/tasks/task_extractor.go
+++ b/backend/plugins/zentao/tasks/task_extractor.go
@@ -64,6 +64,11 @@ func ExtractTask(taskCtx plugin.SubTaskContext) errors.Error 
{
                        if err != nil {
                                return nil, errors.Default.WrapRaw(err)
                        }
+
+                       // set storyList and FromBugList
+                       data.StoryList[res.StoryID] = res.Story
+                       data.FromBugList[res.FromBug] = true
+
                        task := &models.ZentaoTask{
                                ConnectionId:       data.Options.ConnectionId,
                                ID:                 res.Id,

Reply via email to