You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@devlake.apache.org by wa...@apache.org on 2022/10/17 06:20:08 UTC

[incubator-devlake] branch feat-plugin-zentao created (now 6a500469)

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

warren pushed a change to branch feat-plugin-zentao
in repository https://gitbox.apache.org/repos/asf/incubator-devlake.git


      at 6a500469 feat:add zentao bugs

This branch includes the following new commits:

     new d0852c82 feat(zentao): create new plugin
     new 74f74a84 feat(zentao): create new plugin
     new 1743a758 fix(zentao): update connection
     new ef1c75fb update project collector
     new 34298a21 feat:add zentao project extractor
     new f0bd0762 fix(zentao): fix minor issues
     new 47f3ecd6 feat(zentao): add execution
     new 9919d22f feat(zentao): fix execution time
     new c1172d00 feat(zentao): add zentao stories
     new 8f3ff2c7 fix:apiurl&model error
     new 23cb348d fix:restore code
     new 6a500469 feat:add zentao bugs

The 12 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails.  The revisions
listed as "add" were already present in the repository and have only
been added to this reference.



[incubator-devlake] 03/12: fix(zentao): update connection

Posted by wa...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

warren pushed a commit to branch feat-plugin-zentao
in repository https://gitbox.apache.org/repos/asf/incubator-devlake.git

commit 1743a758f5ed302c8077fa42f489b34ce05b5467
Author: Yingchu Chen <yi...@merico.dev>
AuthorDate: Sat Sep 10 15:35:59 2022 +0800

    fix(zentao): update connection
---
 plugins/zentao/models/archived/connection.go                       | 2 +-
 plugins/zentao/models/migrationscripts/20220906_add_init_tables.go | 7 ++++---
 2 files changed, 5 insertions(+), 4 deletions(-)

diff --git a/plugins/zentao/models/archived/connection.go b/plugins/zentao/models/archived/connection.go
index 60d7178c..8a44237e 100644
--- a/plugins/zentao/models/archived/connection.go
+++ b/plugins/zentao/models/archived/connection.go
@@ -15,7 +15,7 @@ See the License for the specific language governing permissions and
 limitations under the License.
 */
 
-package models
+package archived
 
 import (
 	"github.com/apache/incubator-devlake/models/migrationscripts/archived"
diff --git a/plugins/zentao/models/migrationscripts/20220906_add_init_tables.go b/plugins/zentao/models/migrationscripts/20220906_add_init_tables.go
index 181bbc77..fa9f9684 100644
--- a/plugins/zentao/models/migrationscripts/20220906_add_init_tables.go
+++ b/plugins/zentao/models/migrationscripts/20220906_add_init_tables.go
@@ -19,19 +19,20 @@ package migrationscripts
 
 import (
 	"context"
+	"github.com/apache/incubator-devlake/plugins/zentao/models/archived"
 	"gorm.io/gorm"
 )
 
-type addInitTables struct {}
+type addInitTables struct{}
 
 func (u *addInitTables) Up(ctx context.Context, db *gorm.DB) error {
 	return db.Migrator().AutoMigrate(
-		// TODO add you models
+		archived.ZentaoConnection{},
 	)
 }
 
 func (*addInitTables) Version() uint64 {
-	return 20220906000001
+	return 20220910000001
 }
 
 func (*addInitTables) Name() string {


[incubator-devlake] 11/12: fix:restore code

Posted by wa...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

warren pushed a commit to branch feat-plugin-zentao
in repository https://gitbox.apache.org/repos/asf/incubator-devlake.git

commit 23cb348d90a3c46d69a7769d0c2c388d0a537532
Author: yuqiangabab <11...@qq.com>
AuthorDate: Sun Oct 2 11:26:48 2022 +0800

    fix:restore code
---
 plugins/zentao/impl/impl.go                                    | 10 +++++-----
 .../zentao/models/migrationscripts/20220906_add_init_tables.go |  6 +++---
 plugins/zentao/tasks/task_data.go                              |  1 -
 3 files changed, 8 insertions(+), 9 deletions(-)

diff --git a/plugins/zentao/impl/impl.go b/plugins/zentao/impl/impl.go
index 9b6b8f0b..aaf3c922 100644
--- a/plugins/zentao/impl/impl.go
+++ b/plugins/zentao/impl/impl.go
@@ -52,11 +52,11 @@ func (plugin Zentao) Init(config *viper.Viper, logger core.Logger, db *gorm.DB)
 func (plugin Zentao) SubTaskMetas() []core.SubTaskMeta {
 	// TODO add your sub task here
 	return []core.SubTaskMeta{
-		//tasks.CollectProjectMeta,
-		//tasks.ExtractProjectsMeta,
-		//tasks.CollectExecutionMeta,
-		//tasks.ExtractExecutionsMeta,
-		//tasks.ConvertExecutionsMeta,
+		tasks.CollectProjectMeta,
+		tasks.ExtractProjectsMeta,
+		tasks.CollectExecutionMeta,
+		tasks.ExtractExecutionsMeta,
+		tasks.ConvertExecutionsMeta,
 		tasks.CollectStoriesMeta,
 		tasks.ExtractStoriesMeta,
 		tasks.ConvertStoriesMeta,
diff --git a/plugins/zentao/models/migrationscripts/20220906_add_init_tables.go b/plugins/zentao/models/migrationscripts/20220906_add_init_tables.go
index 2213d937..3d51dcde 100644
--- a/plugins/zentao/models/migrationscripts/20220906_add_init_tables.go
+++ b/plugins/zentao/models/migrationscripts/20220906_add_init_tables.go
@@ -27,9 +27,9 @@ type addInitTables struct{}
 
 func (u *addInitTables) Up(ctx context.Context, db *gorm.DB) error {
 	return db.Migrator().AutoMigrate(
-		//archived.ZentaoConnection{},
-		//archived.ZentaoProject{},
-		//archived.ZentaoExecution{},
+		archived.ZentaoConnection{},
+		archived.ZentaoProject{},
+		archived.ZentaoExecution{},
 		archived.ZentaoStories{},
 	)
 }
diff --git a/plugins/zentao/tasks/task_data.go b/plugins/zentao/tasks/task_data.go
index c522360e..a2d88f22 100644
--- a/plugins/zentao/tasks/task_data.go
+++ b/plugins/zentao/tasks/task_data.go
@@ -40,7 +40,6 @@ type ZentaoOptions struct {
 	ProjectId    uint64
 	Tasks        []string `json:"tasks,omitempty"`
 	Since        string
-	StoriesId    uint64
 }
 
 type ZentaoTaskData struct {


[incubator-devlake] 09/12: feat(zentao): add zentao stories

Posted by wa...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

warren pushed a commit to branch feat-plugin-zentao
in repository https://gitbox.apache.org/repos/asf/incubator-devlake.git

commit c1172d0054115354e782618d1aa1fb6816453d2f
Author: yuqiangabab <11...@qq.com>
AuthorDate: Sun Sep 25 13:40:52 2022 +0800

    feat(zentao): add zentao stories
---
 plugins/zentao/impl/impl.go                        |  13 +-
 plugins/zentao/models/archived/stories.go          | 163 +++++++++++++++++++++
 .../migrationscripts/20220906_add_init_tables.go   |   3 +-
 plugins/zentao/models/stories.go                   | 163 +++++++++++++++++++++
 plugins/zentao/tasks/stories_collector.go          |  77 ++++++++++
 plugins/zentao/tasks/stories_convertor.go          | 110 ++++++++++++++
 plugins/zentao/tasks/stories_extractor.go          |  65 ++++++++
 plugins/zentao/tasks/task_data.go                  |   2 +
 plugins/zentao/zentao.go                           |   2 +
 9 files changed, 592 insertions(+), 6 deletions(-)

diff --git a/plugins/zentao/impl/impl.go b/plugins/zentao/impl/impl.go
index b4004753..9b6b8f0b 100644
--- a/plugins/zentao/impl/impl.go
+++ b/plugins/zentao/impl/impl.go
@@ -52,11 +52,14 @@ func (plugin Zentao) Init(config *viper.Viper, logger core.Logger, db *gorm.DB)
 func (plugin Zentao) SubTaskMetas() []core.SubTaskMeta {
 	// TODO add your sub task here
 	return []core.SubTaskMeta{
-		tasks.CollectProjectMeta,
-		tasks.ExtractProjectsMeta,
-		tasks.CollectExecutionMeta,
-		tasks.ExtractExecutionsMeta,
-		tasks.ConvertExecutionsMeta,
+		//tasks.CollectProjectMeta,
+		//tasks.ExtractProjectsMeta,
+		//tasks.CollectExecutionMeta,
+		//tasks.ExtractExecutionsMeta,
+		//tasks.ConvertExecutionsMeta,
+		tasks.CollectStoriesMeta,
+		tasks.ExtractStoriesMeta,
+		tasks.ConvertStoriesMeta,
 	}
 }
 
diff --git a/plugins/zentao/models/archived/stories.go b/plugins/zentao/models/archived/stories.go
new file mode 100644
index 00000000..cd608682
--- /dev/null
+++ b/plugins/zentao/models/archived/stories.go
@@ -0,0 +1,163 @@
+/*
+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 archived
+
+import (
+	"github.com/apache/incubator-devlake/models/migrationscripts/archived"
+	"github.com/apache/incubator-devlake/plugins/helper"
+)
+
+type ZentaoStories struct {
+	archived.NoPKModel
+	ConnectionId uint64 `gorm:"primaryKey;type:BIGINT  NOT NULL"`
+	ID           int    `json:"id"gorm:"primaryKey;type:BIGINT  NOT NULL"`
+	Vision       string `json:"vision"`
+	Parent       int    `json:"parent"`
+	Product      int    `json:"product"`
+	Branch       int    `json:"branch"`
+	Module       int    `json:"module"`
+	Plan         string `json:"plan"`
+	Source       string `json:"source"`
+	SourceNote   string `json:"sourceNote"`
+	FromBug      int    `json:"fromBug"`
+	Feedback     int    `json:"feedback"`
+	Title        string `json:"title"`
+	Keywords     string `json:"keywords"`
+	Type         string `json:"type"`
+	Category     string `json:"category"`
+	Pri          int    `json:"pri"`
+	Estimate     int    `json:"estimate"`
+	Status       string `json:"status"`
+	SubStatus    string `json:"subStatus"`
+	Color        string `json:"color"`
+	Stage        string `json:"stage"`
+	StagedBy     string `json:"stagedBy"`
+	//Mailto           []interface{} `json:"mailto" gorm:"-:all"`
+	Lib              int `json:"lib"`
+	FromStory        int `json:"fromStory"`
+	FromVersion      int `json:"fromVersion"`
+	OpenedBy         `json:"openedBy"`
+	OpenedDate       *helper.Iso8601Time `json:"openedDate"`
+	AssignedTo       `json:"assignedTo"`
+	AssignedDate     *helper.Iso8601Time `json:"assignedDate"`
+	ApprovedDate     string              `json:"approvedDate"`
+	LastEditedBy     `json:"lastEditedBy"`
+	LastEditedDate   *helper.Iso8601Time `json:"lastEditedDate"`
+	ChangedBy        string              `json:"changedBy"`
+	ChangedDate      string              `json:"changedDate"`
+	ReviewedBy       interface{}         `json:"reviewedBy" gorm:"-:all"`
+	ReviewedDate     *helper.Iso8601Time `json:"reviewedDate"`
+	ClosedBy         `json:"closedBy"`
+	ClosedDate       *helper.Iso8601Time `json:"closedDate"`
+	ClosedReason     string              `json:"closedReason"`
+	ActivatedDate    string              `json:"activatedDate"`
+	ToBug            int                 `json:"toBug"`
+	ChildStories     string              `json:"childStories"`
+	LinkStories      string              `json:"linkStories"`
+	LinkRequirements string              `json:"linkRequirements"`
+	DuplicateStory   int                 `json:"duplicateStory"`
+	Version          int                 `json:"version"`
+	StoryChanged     string              `json:"storyChanged"`
+	FeedbackBy       string              `json:"feedbackBy"`
+	NotifyEmail      string              `json:"notifyEmail"`
+	URChanged        string              `json:"URChanged"`
+	Deleted          bool                `json:"deleted"`
+	Spec             string              `json:"spec"`
+	Verify           string              `json:"verify"`
+	Executions       Executions          `json:"executions" gorm:"-:all"`
+	Tasks            []Tasks             `json:"tasks" gorm:"-:all"`
+	//Stages           []interface{}       `json:"stages" gorm:"-:all"`
+	PlanTitle []string `json:"planTitle" gorm:"-:all"`
+	//Children         []interface{}       `json:"children" gorm:"-:all"`
+	//Files            []interface{}       `json:"files" gorm:"-:all"`
+	ProductName   string  `json:"productName"`
+	ProductStatus string  `json:"productStatus"`
+	ModuleTitle   string  `json:"moduleTitle"`
+	Bugs          []Bugs  `json:"bugs" gorm:"-:all"`
+	Cases         []Cases `json:"cases" gorm:"-:all"`
+	//Requirements  []interface{} `json:"requirements" gorm:"-:all"`
+	Actions    []Actions `json:"actions" gorm:"-:all"`
+	PreAndNext `json:"preAndNext"`
+}
+type Executions struct {
+	Num1 struct {
+		Project int    `json:"project"`
+		Name    string `json:"name"`
+		Status  string `json:"status"`
+		Type    string `json:"type"`
+	} `json:"1"`
+}
+type Tasks struct {
+	ID         int    `json:"id"`
+	Name       string `json:"name"`
+	Type       string `json:"type"`
+	Status     string `json:"status"`
+	AssignedTo struct {
+		ID       int    `json:"id"`
+		Account  string `json:"account"`
+		Avatar   string `json:"avatar"`
+		Realname string `json:"realname"`
+	} `json:"assignedTo"`
+}
+type Bugs struct {
+	ID       int    `json:"id"`
+	Title    string `json:"title"`
+	Status   string `json:"status"`
+	Pri      int    `json:"pri"`
+	Severity int    `json:"severity"`
+}
+type Cases struct {
+	ID     int    `json:"id"`
+	Title  string `json:"title"`
+	Pri    int    `json:"pri"`
+	Status string `json:"status"`
+}
+
+type Actions struct {
+	ID         int    `json:"id"`
+	ObjectType string `json:"objectType"`
+	ObjectID   int    `json:"objectID"`
+	Product    string `json:"product"`
+	Project    int    `json:"project"`
+	Execution  int    `json:"execution"`
+	Actor      string `json:"actor"`
+	Action     string `json:"action"`
+	Date       string `json:"date"`
+	Comment    string `json:"comment"`
+	Extra      string `json:"extra"`
+	Read       string `json:"read"`
+	Vision     string `json:"vision"`
+	Efforted   int    `json:"efforted"`
+	//History    []interface{} `json:"history"`
+	Desc string `json:"desc"`
+}
+type AssignedTo struct {
+	ID       int    `json:"id"`
+	Account  string `json:"account"`
+	Avatar   string `json:"avatar"`
+	Realname string `json:"realname"`
+}
+
+type PreAndNext struct {
+	Pre  string `json:"pre"`
+	Next string `json:"next"`
+}
+
+func (ZentaoStories) TableName() string {
+	return "_tool_zentao_stories"
+}
diff --git a/plugins/zentao/models/migrationscripts/20220906_add_init_tables.go b/plugins/zentao/models/migrationscripts/20220906_add_init_tables.go
index 6635dda3..2213d937 100644
--- a/plugins/zentao/models/migrationscripts/20220906_add_init_tables.go
+++ b/plugins/zentao/models/migrationscripts/20220906_add_init_tables.go
@@ -29,7 +29,8 @@ func (u *addInitTables) Up(ctx context.Context, db *gorm.DB) error {
 	return db.Migrator().AutoMigrate(
 		//archived.ZentaoConnection{},
 		//archived.ZentaoProject{},
-		archived.ZentaoExecution{},
+		//archived.ZentaoExecution{},
+		archived.ZentaoStories{},
 	)
 }
 
diff --git a/plugins/zentao/models/stories.go b/plugins/zentao/models/stories.go
new file mode 100644
index 00000000..be198bbf
--- /dev/null
+++ b/plugins/zentao/models/stories.go
@@ -0,0 +1,163 @@
+/*
+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 models
+
+import (
+	"github.com/apache/incubator-devlake/models/common"
+	"github.com/apache/incubator-devlake/plugins/helper"
+)
+
+type ZentaoStories struct {
+	common.NoPKModel
+	ConnectionId uint64 `gorm:"primaryKey;type:BIGINT  NOT NULL"`
+	ID           int    `json:"id"gorm:"primaryKey;type:BIGINT  NOT NULL"`
+	Vision       string `json:"vision"`
+	Parent       int    `json:"parent"`
+	Product      int    `json:"product"`
+	Branch       int    `json:"branch"`
+	Module       int    `json:"module"`
+	Plan         string `json:"plan"`
+	Source       string `json:"source"`
+	SourceNote   string `json:"sourceNote"`
+	FromBug      int    `json:"fromBug"`
+	Feedback     int    `json:"feedback"`
+	Title        string `json:"title"`
+	Keywords     string `json:"keywords"`
+	Type         string `json:"type"`
+	Category     string `json:"category"`
+	Pri          int    `json:"pri"`
+	Estimate     int    `json:"estimate"`
+	Status       string `json:"status"`
+	SubStatus    string `json:"subStatus"`
+	Color        string `json:"color"`
+	Stage        string `json:"stage"`
+	StagedBy     string `json:"stagedBy"`
+	//Mailto           []interface{} `json:"mailto" gorm:"-:all"`
+	Lib              int `json:"lib"`
+	FromStory        int `json:"fromStory"`
+	FromVersion      int `json:"fromVersion"`
+	OpenedBy         `json:"openedBy"`
+	OpenedDate       *helper.Iso8601Time `json:"openedDate"`
+	AssignedTo       `json:"assignedTo"`
+	AssignedDate     *helper.Iso8601Time `json:"assignedDate"`
+	ApprovedDate     string              `json:"approvedDate"`
+	LastEditedBy     `json:"lastEditedBy"`
+	LastEditedDate   *helper.Iso8601Time `json:"lastEditedDate"`
+	ChangedBy        string              `json:"changedBy"`
+	ChangedDate      string              `json:"changedDate"`
+	ReviewedBy       interface{}         `json:"reviewedBy" gorm:"-:all"`
+	ReviewedDate     *helper.Iso8601Time `json:"reviewedDate"`
+	ClosedBy         `json:"closedBy"`
+	ClosedDate       *helper.Iso8601Time `json:"closedDate"`
+	ClosedReason     string              `json:"closedReason"`
+	ActivatedDate    string              `json:"activatedDate"`
+	ToBug            int                 `json:"toBug"`
+	ChildStories     string              `json:"childStories"`
+	LinkStories      string              `json:"linkStories"`
+	LinkRequirements string              `json:"linkRequirements"`
+	DuplicateStory   int                 `json:"duplicateStory"`
+	Version          int                 `json:"version"`
+	StoryChanged     string              `json:"storyChanged"`
+	FeedbackBy       string              `json:"feedbackBy"`
+	NotifyEmail      string              `json:"notifyEmail"`
+	URChanged        string              `json:"URChanged"`
+	Deleted          bool                `json:"deleted"`
+	Spec             string              `json:"spec"`
+	Verify           string              `json:"verify"`
+	Executions       Executions          `json:"executions" gorm:"-:all"`
+	Tasks            []Tasks             `json:"tasks" gorm:"-:all"`
+	//Stages           []interface{}       `json:"stages" gorm:"-:all"`
+	PlanTitle []string `json:"planTitle" gorm:"-:all"`
+	//Children         []interface{}       `json:"children" gorm:"-:all"`
+	//Files            []interface{}       `json:"files" gorm:"-:all"`
+	ProductName   string  `json:"productName"`
+	ProductStatus string  `json:"productStatus"`
+	ModuleTitle   string  `json:"moduleTitle"`
+	Bugs          []Bugs  `json:"bugs" gorm:"-:all"`
+	Cases         []Cases `json:"cases" gorm:"-:all"`
+	//Requirements  []interface{} `json:"requirements" gorm:"-:all"`
+	Actions    []Actions `json:"actions" gorm:"-:all"`
+	PreAndNext `json:"preAndNext"`
+}
+type Executions struct {
+	Num1 struct {
+		Project int    `json:"project"`
+		Name    string `json:"name"`
+		Status  string `json:"status"`
+		Type    string `json:"type"`
+	} `json:"1"`
+}
+type Tasks struct {
+	ID         int    `json:"id"`
+	Name       string `json:"name"`
+	Type       string `json:"type"`
+	Status     string `json:"status"`
+	AssignedTo struct {
+		ID       int    `json:"id"`
+		Account  string `json:"account"`
+		Avatar   string `json:"avatar"`
+		Realname string `json:"realname"`
+	} `json:"assignedTo"`
+}
+type Bugs struct {
+	ID       int    `json:"id"`
+	Title    string `json:"title"`
+	Status   string `json:"status"`
+	Pri      int    `json:"pri"`
+	Severity int    `json:"severity"`
+}
+type Cases struct {
+	ID     int    `json:"id"`
+	Title  string `json:"title"`
+	Pri    int    `json:"pri"`
+	Status string `json:"status"`
+}
+
+type Actions struct {
+	ID         int    `json:"id"`
+	ObjectType string `json:"objectType"`
+	ObjectID   int    `json:"objectID"`
+	Product    string `json:"product"`
+	Project    int    `json:"project"`
+	Execution  int    `json:"execution"`
+	Actor      string `json:"actor"`
+	Action     string `json:"action"`
+	Date       string `json:"date"`
+	Comment    string `json:"comment"`
+	Extra      string `json:"extra"`
+	Read       string `json:"read"`
+	Vision     string `json:"vision"`
+	Efforted   int    `json:"efforted"`
+	//History    []interface{} `json:"history"`
+	Desc string `json:"desc"`
+}
+type AssignedTo struct {
+	ID       int    `json:"id"`
+	Account  string `json:"account"`
+	Avatar   string `json:"avatar"`
+	Realname string `json:"realname"`
+}
+
+type PreAndNext struct {
+	Pre  string `json:"pre"`
+	Next string `json:"next"`
+}
+
+func (ZentaoStories) TableName() string {
+	return "_tool_zentao_stories"
+}
diff --git a/plugins/zentao/tasks/stories_collector.go b/plugins/zentao/tasks/stories_collector.go
new file mode 100644
index 00000000..6ba58924
--- /dev/null
+++ b/plugins/zentao/tasks/stories_collector.go
@@ -0,0 +1,77 @@
+/*
+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/plugins/core"
+	"github.com/apache/incubator-devlake/plugins/helper"
+	"io"
+	"net/http"
+	"net/url"
+)
+
+const RAW_STORIES_TABLE = "zentao_stories"
+
+var _ core.SubTaskEntryPoint = CollectExecution
+
+func CollectStories(taskCtx core.SubTaskContext) error {
+	data := taskCtx.GetData().(*ZentaoTaskData)
+	collector, err := helper.NewApiCollector(helper.ApiCollectorArgs{
+		RawDataSubTaskArgs: helper.RawDataSubTaskArgs{
+			Ctx: taskCtx,
+			Params: ZentaoApiParams{
+				StoriesId: data.Options.StoriesId,
+			},
+			Table: RAW_STORIES_TABLE,
+		},
+		ApiClient:   data.ApiClient,
+		Incremental: false,
+		PageSize:    100,
+		// TODO write which api would you want request
+		UrlTemplate: "/stories/{{ .Params.StoriesId }}",
+		Query: func(reqData *helper.RequestData) (url.Values, 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
+		},
+		GetTotalPages: GetTotalPagesFromResponse,
+		ResponseParser: func(res *http.Response) ([]json.RawMessage, error) {
+			body, err := io.ReadAll(res.Body)
+			res.Body.Close()
+			if err != nil {
+				return nil, err
+			}
+			return []json.RawMessage{body}, nil
+		},
+	})
+	if err != nil {
+		return err
+	}
+
+	return collector.Execute()
+}
+
+var CollectStoriesMeta = core.SubTaskMeta{
+	Name:             "CollectStories",
+	EntryPoint:       CollectStories,
+	EnabledByDefault: true,
+	Description:      "Collect Stories data from Zentao api",
+}
diff --git a/plugins/zentao/tasks/stories_convertor.go b/plugins/zentao/tasks/stories_convertor.go
new file mode 100644
index 00000000..a41a1868
--- /dev/null
+++ b/plugins/zentao/tasks/stories_convertor.go
@@ -0,0 +1,110 @@
+/*
+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 (
+	"github.com/apache/incubator-devlake/models/domainlayer"
+	"github.com/apache/incubator-devlake/models/domainlayer/didgen"
+	"github.com/apache/incubator-devlake/models/domainlayer/ticket"
+	"github.com/apache/incubator-devlake/plugins/core"
+	"github.com/apache/incubator-devlake/plugins/core/dal"
+	"github.com/apache/incubator-devlake/plugins/helper"
+	"github.com/apache/incubator-devlake/plugins/zentao/models"
+	"reflect"
+)
+
+var _ core.SubTaskEntryPoint = ConvertExecutions
+
+var ConvertStoriesMeta = core.SubTaskMeta{
+	Name:             "convertStories",
+	EntryPoint:       ConvertStories,
+	EnabledByDefault: true,
+	Description:      "convert Zentao stories",
+	DomainTypes:      []string{core.DOMAIN_TYPE_TICKET},
+}
+
+func ConvertStories(taskCtx core.SubTaskContext) error {
+	data := taskCtx.GetData().(*ZentaoTaskData)
+	db := taskCtx.GetDal()
+	boardIdGen := didgen.NewDomainIdGenerator(&models.ZentaoStories{})
+	cursor, err := db.Cursor(
+		dal.From(&models.ZentaoStories{}),
+		dal.Where(`_tool_zentao_stories.id = ? and 
+			_tool_zentao_stories.connection_id = ?`, data.Options.StoriesId, data.Options.ConnectionId),
+	)
+	if err != nil {
+		return err
+	}
+	defer cursor.Close()
+	convertor, err := helper.NewDataConverter(helper.DataConverterArgs{
+		InputRowType: reflect.TypeOf(models.ZentaoStories{}),
+		Input:        cursor,
+		RawDataSubTaskArgs: helper.RawDataSubTaskArgs{
+			Ctx: taskCtx,
+			Params: ZentaoApiParams{
+				StoriesId: data.Options.StoriesId,
+			},
+			Table: RAW_STORIES_TABLE,
+		},
+		Convert: func(inputRow interface{}) ([]interface{}, error) {
+			toolStories := inputRow.(*models.ZentaoStories)
+
+			domainBoard := &ticket.Issue{
+				DomainEntity: domainlayer.DomainEntity{
+					Id: boardIdGen.Generate(toolStories.ConnectionId, toolStories.ID),
+				},
+				Url:                     "",
+				IconURL:                 "",
+				IssueKey:                "",
+				Title:                   toolStories.Title,
+				Description:             toolStories.Spec,
+				EpicKey:                 "",
+				Type:                    toolStories.Type,
+				Status:                  toolStories.Status,
+				OriginalStatus:          "",
+				StoryPoint:              0,
+				ResolutionDate:          nil,
+				CreatedDate:             toolStories.OpenedDate.ToNullableTime(),
+				UpdatedDate:             toolStories.LastEditedDate.ToNullableTime(),
+				LeadTimeMinutes:         0,
+				ParentIssueId:           "",
+				Priority:                "",
+				OriginalEstimateMinutes: 0,
+				TimeSpentMinutes:        0,
+				TimeRemainingMinutes:    0,
+				CreatorId:               "",
+				CreatorName:             toolStories.OpenedBy.OpenedByRealname,
+				AssigneeId:              "",
+				AssigneeName:            toolStories.AssignedTo.Realname,
+				Severity:                "",
+				Component:               "",
+				DeploymentId:            "",
+			}
+
+			results := make([]interface{}, 0)
+			results = append(results, domainBoard)
+			return results, nil
+		},
+	})
+
+	if err != nil {
+		return err
+	}
+
+	return convertor.Execute()
+}
diff --git a/plugins/zentao/tasks/stories_extractor.go b/plugins/zentao/tasks/stories_extractor.go
new file mode 100644
index 00000000..aaadfb84
--- /dev/null
+++ b/plugins/zentao/tasks/stories_extractor.go
@@ -0,0 +1,65 @@
+/*
+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/plugins/core"
+	"github.com/apache/incubator-devlake/plugins/helper"
+	"github.com/apache/incubator-devlake/plugins/zentao/models"
+)
+
+var _ core.SubTaskEntryPoint = ExtractStories
+
+var ExtractStoriesMeta = core.SubTaskMeta{
+	Name:             "extractStories",
+	EntryPoint:       ExtractStories,
+	EnabledByDefault: true,
+	Description:      "extract Zentao stories",
+	DomainTypes:      []string{core.DOMAIN_TYPE_TICKET},
+}
+
+func ExtractStories(taskCtx core.SubTaskContext) error {
+	data := taskCtx.GetData().(*ZentaoTaskData)
+	extractor, err := helper.NewApiExtractor(helper.ApiExtractorArgs{
+		RawDataSubTaskArgs: helper.RawDataSubTaskArgs{
+			Ctx: taskCtx,
+			Params: ZentaoApiParams{
+				StoriesId: data.Options.StoriesId,
+			},
+			Table: RAW_STORIES_TABLE,
+		},
+		Extract: func(row *helper.RawData) ([]interface{}, error) {
+			stories := &models.ZentaoStories{}
+			err := json.Unmarshal(row.Data, stories)
+			if err != nil {
+				return nil, err
+			}
+			stories.ConnectionId = data.Options.ConnectionId
+			results := make([]interface{}, 0)
+			results = append(results, stories)
+			return results, nil
+		},
+	})
+
+	if err != nil {
+		return err
+	}
+
+	return extractor.Execute()
+}
diff --git a/plugins/zentao/tasks/task_data.go b/plugins/zentao/tasks/task_data.go
index a2d88f22..ae51908f 100644
--- a/plugins/zentao/tasks/task_data.go
+++ b/plugins/zentao/tasks/task_data.go
@@ -27,6 +27,7 @@ type ZentaoApiParams struct {
 	ProductId   uint64
 	ExecutionId uint64
 	ProjectId   uint64
+	StoriesId   uint64
 }
 
 type ZentaoOptions struct {
@@ -40,6 +41,7 @@ type ZentaoOptions struct {
 	ProjectId    uint64
 	Tasks        []string `json:"tasks,omitempty"`
 	Since        string
+	StoriesId    uint64
 }
 
 type ZentaoTaskData struct {
diff --git a/plugins/zentao/zentao.go b/plugins/zentao/zentao.go
index 32845c90..3768b5d9 100644
--- a/plugins/zentao/zentao.go
+++ b/plugins/zentao/zentao.go
@@ -34,6 +34,7 @@ func main() {
 	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")
+	storiesId := cmd.Flags().IntP("storiesId", "s", 1, "stories id")
 
 	cmd.Run = func(cmd *cobra.Command, args []string) {
 		runner.DirectRun(cmd, args, PluginEntry, map[string]interface{}{
@@ -41,6 +42,7 @@ func main() {
 			"executionId":  *executionId,
 			"productId":    *productId,
 			"projectId":    *projectId,
+			"storiesId":    *storiesId,
 		})
 	}
 	runner.RunCmd(cmd)


[incubator-devlake] 01/12: feat(zentao): create new plugin

Posted by wa...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

warren pushed a commit to branch feat-plugin-zentao
in repository https://gitbox.apache.org/repos/asf/incubator-devlake.git

commit d0852c8257b5e2ea3b54f7249d91983fd156c15f
Author: Yingchu Chen <yi...@merico.dev>
AuthorDate: Tue Sep 6 18:03:04 2022 +0800

    feat(zentao): create new plugin
    
    Relate to #2961
---
 plugins/zentao/api/blueprint.go                    |  69 ++++++++++
 plugins/zentao/api/connection.go                   | 149 +++++++++++++++++++++
 plugins/zentao/api/init.go                         |  39 ++++++
 plugins/zentao/impl/impl.go                        | 124 +++++++++++++++++
 plugins/zentao/models/archived/connection.go       |  70 ++++++++++
 plugins/zentao/models/connection.go                |  51 +++++++
 .../migrationscripts/20220906_add_init_tables.go   |  39 ++++++
 plugins/zentao/models/migrationscripts/register.go |  27 ++++
 plugins/zentao/tasks/api_client.go                 |  92 +++++++++++++
 plugins/zentao/tasks/project_collector.go          |  78 +++++++++++
 plugins/zentao/tasks/task_data.go                  |  61 +++++++++
 plugins/zentao/zentao.go                           |  47 +++++++
 12 files changed, 846 insertions(+)

diff --git a/plugins/zentao/api/blueprint.go b/plugins/zentao/api/blueprint.go
new file mode 100644
index 00000000..3fa0e25f
--- /dev/null
+++ b/plugins/zentao/api/blueprint.go
@@ -0,0 +1,69 @@
+/*
+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/plugins/core"
+	"github.com/apache/incubator-devlake/plugins/helper"
+	"github.com/apache/incubator-devlake/plugins/zentao/tasks"
+)
+
+func MakePipelinePlan(subtaskMetas []core.SubTaskMeta, connectionId uint64, scope []*core.BlueprintScopeV100) (core.PipelinePlan, error) {
+	var err error
+	plan := make(core.PipelinePlan, len(scope))
+	for i, scopeElem := range scope {
+		taskOptions := make(map[string]interface{})
+		err = json.Unmarshal(scopeElem.Options, &taskOptions)
+		if err != nil {
+			return nil, err
+		}
+		taskOptions["connectionId"] = connectionId
+
+		//TODO Add transformation rules to task options
+
+        /*
+        var transformationRules tasks.TransformationRules
+        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, err
+		}
+		// subtasks
+		subtasks, err := helper.MakePipelinePlanSubtasks(subtaskMetas, scopeElem.Entities)
+		if err != nil {
+			return nil, err
+		}
+		plan[i] = core.PipelineStage{
+			{
+				Plugin:   "zentao",
+				Subtasks: subtasks,
+				Options:  taskOptions,
+			},
+		}
+	}
+	return plan, nil
+}
diff --git a/plugins/zentao/api/connection.go b/plugins/zentao/api/connection.go
new file mode 100644
index 00000000..2f8183d5
--- /dev/null
+++ b/plugins/zentao/api/connection.go
@@ -0,0 +1,149 @@
+/*
+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"
+	"github.com/apache/incubator-devlake/errors"
+	"github.com/apache/incubator-devlake/plugins/core"
+	"github.com/apache/incubator-devlake/plugins/feishu/apimodels"
+	"github.com/apache/incubator-devlake/plugins/helper"
+	"github.com/apache/incubator-devlake/plugins/zentao/models"
+	"github.com/mitchellh/mapstructure"
+	"net/http"
+)
+
+//TODO Please modify the following code to fit your needs
+func TestConnection(input *core.ApiResourceInput) (*core.ApiResourceOutput, error) {
+	// process input
+	var params models.TestConnectionRequest
+	err := mapstructure.Decode(input.Body, &params)
+	if err != nil {
+		return nil, errors.BadInput.Wrap(err, "could not decode request parameters", errors.AsUserMessage())
+	}
+	err = vld.Struct(params)
+	if err != nil {
+		return nil, errors.BadInput.Wrap(err, "could not validate request parameters", errors.AsUserMessage())
+	}
+
+	authApiClient, err := helper.NewApiClient(context.TODO(), params.Endpoint, nil, 0, params.Proxy, basicRes)
+	if err != nil {
+		return nil, err
+	}
+
+	// request for access token
+	tokenReqBody := &apimodels.ApiAccessTokenRequest{
+		AppId:     params.Username,
+		AppSecret: params.Password,
+	}
+	tokenRes, err := authApiClient.Post("/tokens", nil, tokenReqBody, nil)
+	if err != nil {
+		return nil, err
+	}
+	tokenResBody := &apimodels.ApiAccessTokenResponse{}
+	err = helper.UnmarshalResponse(tokenRes, tokenResBody)
+	if err != nil {
+		return nil, err
+	}
+	if tokenResBody.AppAccessToken == "" && tokenResBody.TenantAccessToken == "" {
+		return nil, errors.Default.New("failed to request access token")
+	}
+
+	// output
+	return nil, nil
+}
+
+//TODO Please modify the folowing code to adapt to your plugin
+/*
+POST /plugins/Zentao/connections
+{
+	"name": "Zentao data connection name",
+	"endpoint": "Zentao api endpoint, i.e. https://example.com",
+	"username": "username, usually should be email address",
+	"password": "Zentao api access token"
+}
+*/
+func PostConnections(input *core.ApiResourceInput) (*core.ApiResourceOutput, error) {
+	// update from request and save to database
+	connection := &models.ZentaoConnection{}
+	err := connectionHelper.Create(connection, input)
+	if err != nil {
+		return nil, err
+	}
+	return &core.ApiResourceOutput{Body: connection, Status: http.StatusOK}, nil
+}
+
+//TODO Please modify the folowing code to adapt to your plugin
+/*
+PATCH /plugins/Zentao/connections/:connectionId
+{
+	"name": "Zentao data connection name",
+	"endpoint": "Zentao api endpoint, i.e. https://example.com",
+	"username": "username, usually should be email address",
+	"password": "Zentao api access token"
+}
+*/
+func PatchConnection(input *core.ApiResourceInput) (*core.ApiResourceOutput, error) {
+	connection := &models.ZentaoConnection{}
+	err := connectionHelper.Patch(connection, input)
+	if err != nil {
+		return nil, err
+	}
+	return &core.ApiResourceOutput{Body: connection}, nil
+}
+
+/*
+DELETE /plugins/Zentao/connections/:connectionId
+*/
+func DeleteConnection(input *core.ApiResourceInput) (*core.ApiResourceOutput, error) {
+	connection := &models.ZentaoConnection{}
+	err := connectionHelper.First(connection, input.Params)
+	if err != nil {
+		return nil, err
+	}
+	err = connectionHelper.Delete(connection)
+	return &core.ApiResourceOutput{Body: connection}, err
+}
+
+/*
+GET /plugins/Zentao/connections
+*/
+func ListConnections(input *core.ApiResourceInput) (*core.ApiResourceOutput, error) {
+	var connections []models.ZentaoConnection
+	err := connectionHelper.List(&connections)
+	if err != nil {
+		return nil, err
+	}
+	return &core.ApiResourceOutput{Body: connections, Status: http.StatusOK}, nil
+}
+
+//TODO Please modify the folowing code to adapt to your plugin
+/*
+GET /plugins/Zentao/connections/:connectionId
+{
+	"name": "Zentao data connection name",
+	"endpoint": "Zentao api endpoint, i.e. https://merico.atlassian.net/rest",
+	"username": "username, usually should be email address",
+	"password": "Zentao api access token"
+}
+*/
+func GetConnection(input *core.ApiResourceInput) (*core.ApiResourceOutput, error) {
+	connection := &models.ZentaoConnection{}
+	err := connectionHelper.First(connection, input.Params)
+	return &core.ApiResourceOutput{Body: connection}, err
+}
diff --git a/plugins/zentao/api/init.go b/plugins/zentao/api/init.go
new file mode 100644
index 00000000..6774e148
--- /dev/null
+++ b/plugins/zentao/api/init.go
@@ -0,0 +1,39 @@
+/*
+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/plugins/core"
+	"github.com/apache/incubator-devlake/plugins/helper"
+	"github.com/go-playground/validator/v10"
+	"github.com/spf13/viper"
+	"gorm.io/gorm"
+)
+
+var vld *validator.Validate
+var connectionHelper *helper.ConnectionApiHelper
+var basicRes core.BasicRes
+
+func Init(config *viper.Viper, logger core.Logger, database *gorm.DB) {
+	basicRes = helper.NewDefaultBasicRes(config, logger, database)
+	vld = validator.New()
+	connectionHelper = helper.NewConnectionHelper(
+		basicRes,
+		vld,
+	)
+}
diff --git a/plugins/zentao/impl/impl.go b/plugins/zentao/impl/impl.go
new file mode 100644
index 00000000..cc81c226
--- /dev/null
+++ b/plugins/zentao/impl/impl.go
@@ -0,0 +1,124 @@
+/*
+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 impl
+
+import (
+	"fmt"
+	"github.com/apache/incubator-devlake/migration"
+	"github.com/apache/incubator-devlake/plugins/core"
+    "github.com/apache/incubator-devlake/plugins/zentao/api"
+    "github.com/apache/incubator-devlake/plugins/zentao/models"
+    "github.com/apache/incubator-devlake/plugins/zentao/models/migrationscripts"
+	"github.com/apache/incubator-devlake/plugins/zentao/tasks"
+	"github.com/apache/incubator-devlake/plugins/helper"
+	"github.com/spf13/viper"
+	"gorm.io/gorm"
+)
+
+// make sure interface is implemented
+var _ core.PluginMeta = (*Zentao)(nil)
+var _ core.PluginInit = (*Zentao)(nil)
+var _ core.PluginTask = (*Zentao)(nil)
+var _ core.PluginApi = (*Zentao)(nil)
+var _ core.PluginBlueprintV100 = (*Zentao)(nil)
+var _ core.CloseablePluginTask = (*Zentao)(nil)
+
+
+
+type Zentao struct{}
+
+func (plugin Zentao) Description() string {
+	return "collect some Zentao data"
+}
+
+func (plugin Zentao) Init(config *viper.Viper, logger core.Logger, db *gorm.DB) error {
+	api.Init(config, logger, db)
+	return nil
+}
+
+func (plugin Zentao) SubTaskMetas() []core.SubTaskMeta {
+	// TODO add your sub task here
+	return []core.SubTaskMeta{
+		tasks.CollectProjectMeta,
+	}
+}
+
+func (plugin Zentao) PrepareTaskData(taskCtx core.TaskContext, options map[string]interface{}) (interface{}, error) {
+	op, err := tasks.DecodeAndValidateTaskOptions(options)
+    if err != nil {
+        return nil, err
+    }
+    connectionHelper := helper.NewConnectionHelper(
+        taskCtx,
+        nil,
+    )
+    connection := &models.ZentaoConnection{}
+    err = connectionHelper.FirstById(connection, op.ConnectionId)
+    if err != nil {
+        return nil, fmt.Errorf("unable to get Zentao connection by the given connection ID: %v", err)
+    }
+
+    apiClient, err := tasks.NewZentaoApiClient(taskCtx, connection)
+    if err != nil {
+        return nil, fmt.Errorf("unable to get Zentao API client instance: %v", err)
+    }
+
+    return &tasks.ZentaoTaskData{
+        Options:   op,
+        ApiClient: apiClient,
+    }, nil
+}
+
+// PkgPath information lost when compiled as plugin(.so)
+func (plugin Zentao) RootPkgPath() string {
+	return "github.com/apache/incubator-devlake/plugins/zentao"
+}
+
+func (plugin Zentao) MigrationScripts() []migration.Script {
+	return migrationscripts.All()
+}
+
+func (plugin Zentao) ApiResources() map[string]map[string]core.ApiResourceHandler {
+    return map[string]map[string]core.ApiResourceHandler{
+        "test": {
+            "POST": api.TestConnection,
+        },
+        "connections": {
+            "POST": api.PostConnections,
+            "GET":  api.ListConnections,
+        },
+        "connections/:connectionId": {
+            "GET":    api.GetConnection,
+            "PATCH":  api.PatchConnection,
+            "DELETE": api.DeleteConnection,
+        },
+    }
+}
+
+func (plugin Zentao) MakePipelinePlan(connectionId uint64, scope []*core.BlueprintScopeV100) (core.PipelinePlan, error) {
+	return api.MakePipelinePlan(plugin.SubTaskMetas(), connectionId, scope)
+}
+
+func (plugin Zentao) Close(taskCtx core.TaskContext) error {
+	data, ok := taskCtx.GetData().(*tasks.ZentaoTaskData)
+	if !ok {
+		return fmt.Errorf("GetData failed when try to close %+v", taskCtx)
+	}
+	data.ApiClient.Release()
+	return nil
+}
diff --git a/plugins/zentao/models/archived/connection.go b/plugins/zentao/models/archived/connection.go
new file mode 100644
index 00000000..60d7178c
--- /dev/null
+++ b/plugins/zentao/models/archived/connection.go
@@ -0,0 +1,70 @@
+/*
+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 models
+
+import (
+	"github.com/apache/incubator-devlake/models/migrationscripts/archived"
+)
+
+//TODO Please modify the following code to fit your needs
+// This object conforms to what the frontend currently sends.
+type ZentaoConnection struct {
+	RestConnection `mapstructure:",squash"`
+	//TODO you may need to use helper.BasicAuth instead of helper.AccessToken
+	BasicAuth `mapstructure:",squash"`
+}
+
+type TestConnectionRequest struct {
+	Endpoint  string `json:"endpoint"`
+	Proxy     string `json:"proxy"`
+	BasicAuth `mapstructure:",squash"`
+}
+
+// This object conforms to what the frontend currently expects.
+type ZentaoResponse struct {
+	Name string `json:"name"`
+	ID   int    `json:"id"`
+	ZentaoConnection
+}
+
+// Using User because it requires authentication.
+type ApiUserResponse struct {
+	Id   int
+	Name string `json:"name"`
+}
+
+func (ZentaoConnection) TableName() string {
+	return "_tool_zentao_connections"
+}
+
+type BasicAuth struct {
+	Username string `mapstructure:"username" validate:"required" json:"username"`
+	Password string `mapstructure:"password" validate:"required" json:"password"`
+}
+
+type RestConnection struct {
+	BaseConnection   `mapstructure:",squash"`
+	Endpoint         string `mapstructure:"endpoint" validate:"required" json:"endpoint"`
+	Proxy            string `mapstructure:"proxy" json:"proxy"`
+	RateLimitPerHour int    `comment:"api request rate limt per hour" json:"rateLimit"`
+}
+
+type BaseConnection struct {
+	Name string `gorm:"type:varchar(100);uniqueIndex" json:"name" validate:"required"`
+	archived.Model
+}
diff --git a/plugins/zentao/models/connection.go b/plugins/zentao/models/connection.go
new file mode 100644
index 00000000..6140d7f4
--- /dev/null
+++ b/plugins/zentao/models/connection.go
@@ -0,0 +1,51 @@
+/*
+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 models
+
+import "github.com/apache/incubator-devlake/plugins/helper"
+
+//TODO Please modify the following code to fit your needs
+// This object conforms to what the frontend currently sends.
+type ZentaoConnection struct {
+	helper.RestConnection `mapstructure:",squash"`
+	//TODO you may need to use helper.BasicAuth instead of helper.AccessToken
+	helper.BasicAuth `mapstructure:",squash"`
+}
+
+type TestConnectionRequest struct {
+	Endpoint         string `json:"endpoint"`
+	Proxy            string `json:"proxy"`
+	helper.BasicAuth `mapstructure:",squash"`
+}
+
+// This object conforms to what the frontend currently expects.
+type ZentaoResponse struct {
+	Name string `json:"name"`
+	ID   int    `json:"id"`
+	ZentaoConnection
+}
+
+// Using User because it requires authentication.
+type ApiUserResponse struct {
+	Id   int
+	Name string `json:"name"`
+}
+
+func (ZentaoConnection) TableName() string {
+	return "_tool_zentao_connections"
+}
diff --git a/plugins/zentao/models/migrationscripts/20220906_add_init_tables.go b/plugins/zentao/models/migrationscripts/20220906_add_init_tables.go
new file mode 100644
index 00000000..181bbc77
--- /dev/null
+++ b/plugins/zentao/models/migrationscripts/20220906_add_init_tables.go
@@ -0,0 +1,39 @@
+/*
+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 migrationscripts
+
+import (
+	"context"
+	"gorm.io/gorm"
+)
+
+type addInitTables struct {}
+
+func (u *addInitTables) Up(ctx context.Context, db *gorm.DB) error {
+	return db.Migrator().AutoMigrate(
+		// TODO add you models
+	)
+}
+
+func (*addInitTables) Version() uint64 {
+	return 20220906000001
+}
+
+func (*addInitTables) Name() string {
+	return "zentao init schemas"
+}
diff --git a/plugins/zentao/models/migrationscripts/register.go b/plugins/zentao/models/migrationscripts/register.go
new file mode 100644
index 00000000..92e20c01
--- /dev/null
+++ b/plugins/zentao/models/migrationscripts/register.go
@@ -0,0 +1,27 @@
+/*
+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 migrationscripts
+
+import "github.com/apache/incubator-devlake/migration"
+
+// All return all the migration scripts
+func All() []migration.Script {
+	return []migration.Script{
+		new(addInitTables),
+	}
+}
diff --git a/plugins/zentao/tasks/api_client.go b/plugins/zentao/tasks/api_client.go
new file mode 100644
index 00000000..62d2d954
--- /dev/null
+++ b/plugins/zentao/tasks/api_client.go
@@ -0,0 +1,92 @@
+/*
+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 (
+	"fmt"
+	"github.com/apache/incubator-devlake/errors"
+	"github.com/apache/incubator-devlake/plugins/feishu/apimodels"
+	"net/http"
+	"strconv"
+	"time"
+
+	"github.com/apache/incubator-devlake/plugins/core"
+	"github.com/apache/incubator-devlake/plugins/helper"
+	"github.com/apache/incubator-devlake/plugins/zentao/models"
+)
+
+func NewZentaoApiClient(taskCtx core.TaskContext, connection *models.ZentaoConnection) (*helper.ApiAsyncClient, error) {
+	authApiClient, err := helper.NewApiClient(taskCtx.GetContext(), connection.Endpoint, nil, 0, connection.Proxy, taskCtx)
+	if err != nil {
+		return nil, err
+	}
+
+	// request for access token
+	tokenReqBody := &apimodels.ApiAccessTokenRequest{
+		AppId:     connection.Username,
+		AppSecret: connection.Password,
+	}
+	tokenRes, err := authApiClient.Post("/tokens", nil, tokenReqBody, nil)
+	if err != nil {
+		return nil, err
+	}
+	tokenResBody := &apimodels.ApiAccessTokenResponse{}
+	err = helper.UnmarshalResponse(tokenRes, tokenResBody)
+	if err != nil {
+		return nil, err
+	}
+	if tokenResBody.AppAccessToken == "" && tokenResBody.TenantAccessToken == "" {
+		return nil, errors.Default.New("failed to request access token")
+	}
+	// real request apiClient
+	apiClient, err := helper.NewApiClient(taskCtx.GetContext(), connection.Endpoint, nil, 0, connection.Proxy, taskCtx)
+	if err != nil {
+		return nil, err
+	}
+	// set token
+	apiClient.SetHeaders(map[string]string{
+		"Token": fmt.Sprintf("%v", tokenResBody.TenantAccessToken),
+	})
+
+	// create rate limit calculator
+	rateLimiter := &helper.ApiRateLimitCalculator{
+		UserRateLimitPerHour: connection.RateLimitPerHour,
+		DynamicRateLimit: func(res *http.Response) (int, time.Duration, error) {
+			rateLimitHeader := res.Header.Get("RateLimit-Limit")
+			if rateLimitHeader == "" {
+				// use default
+				return 0, 0, nil
+			}
+			rateLimit, err := strconv.Atoi(rateLimitHeader)
+			if err != nil {
+				return 0, 0, fmt.Errorf("failed to parse RateLimit-Limit header: %w", err)
+			}
+			// seems like {{ .plugin-ame }} rate limit is on minute basis
+			return rateLimit, 1 * time.Minute, nil
+		},
+	}
+	asyncApiClient, err := helper.CreateAsyncApiClient(
+		taskCtx,
+		apiClient,
+		rateLimiter,
+	)
+	if err != nil {
+		return nil, err
+	}
+	return asyncApiClient, nil
+}
diff --git a/plugins/zentao/tasks/project_collector.go b/plugins/zentao/tasks/project_collector.go
new file mode 100644
index 00000000..7af9dc2c
--- /dev/null
+++ b/plugins/zentao/tasks/project_collector.go
@@ -0,0 +1,78 @@
+/*
+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/plugins/core"
+	"github.com/apache/incubator-devlake/plugins/helper"
+	"net/http"
+	"net/url"
+)
+
+const RAW_PROJECT_TABLE = "zentao_project"
+
+var _ core.SubTaskEntryPoint = CollectProject
+
+func CollectProject(taskCtx core.SubTaskContext) error {
+	data := taskCtx.GetData().(*ZentaoTaskData)
+	iterator, err := helper.NewDateIterator(365)
+	if err != nil {
+		return err
+	}
+
+	collector, err := helper.NewApiCollector(helper.ApiCollectorArgs{
+		RawDataSubTaskArgs: helper.RawDataSubTaskArgs{
+			Ctx:    taskCtx,
+			Params: ZentaoApiParams{},
+			Table:  RAW_PROJECT_TABLE,
+		},
+		ApiClient:   data.ApiClient,
+		Incremental: false,
+		Input:       iterator,
+		PageSize:    100,
+		// TODO write which api would you want request
+		UrlTemplate: "projects",
+		Query: func(reqData *helper.RequestData) (url.Values, 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, error) {
+			var data struct {
+				Projects []json.RawMessage `json:"data"`
+			}
+			err = helper.UnmarshalResponse(res, &data)
+			return data.Projects, err
+		},
+	})
+	if err != nil {
+		return err
+	}
+
+	return collector.Execute()
+}
+
+var CollectProjectMeta = core.SubTaskMeta{
+	Name:             "CollectProject",
+	EntryPoint:       CollectProject,
+	EnabledByDefault: true,
+	Description:      "Collect Project data from Zentao api",
+}
diff --git a/plugins/zentao/tasks/task_data.go b/plugins/zentao/tasks/task_data.go
new file mode 100644
index 00000000..a2d88f22
--- /dev/null
+++ b/plugins/zentao/tasks/task_data.go
@@ -0,0 +1,61 @@
+/*
+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 (
+	"fmt"
+	"github.com/apache/incubator-devlake/plugins/helper"
+	"github.com/mitchellh/mapstructure"
+)
+
+type ZentaoApiParams struct {
+	ProductId   uint64
+	ExecutionId uint64
+	ProjectId   uint64
+}
+
+type ZentaoOptions struct {
+	// TODO add some custom options here if necessary
+	// options means some custom params required by plugin running.
+	// Such As How many rows do your want
+	// You can use it in sub tasks and you need pass it in main.go and pipelines.
+	ConnectionId uint64 `json:"connectionId"`
+	ProductId    uint64
+	ExecutionId  uint64
+	ProjectId    uint64
+	Tasks        []string `json:"tasks,omitempty"`
+	Since        string
+}
+
+type ZentaoTaskData struct {
+	Options   *ZentaoOptions
+	ApiClient *helper.ApiAsyncClient
+}
+
+func DecodeAndValidateTaskOptions(options map[string]interface{}) (*ZentaoOptions, error) {
+	var op ZentaoOptions
+	err := mapstructure.Decode(options, &op)
+	if err != nil {
+		return nil, err
+	}
+
+	if op.ConnectionId == 0 {
+		return nil, fmt.Errorf("connectionId is invalid")
+	}
+	return &op, nil
+}
diff --git a/plugins/zentao/zentao.go b/plugins/zentao/zentao.go
new file mode 100644
index 00000000..32845c90
--- /dev/null
+++ b/plugins/zentao/zentao.go
@@ -0,0 +1,47 @@
+/*
+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 main
+
+import (
+	"github.com/apache/incubator-devlake/plugins/Zentao/impl"
+	"github.com/apache/incubator-devlake/runner"
+	"github.com/spf13/cobra"
+)
+
+// Export a variable named PluginEntry for Framework to search and load
+var PluginEntry impl.Zentao //nolint
+
+// standalone mode for debugging
+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,
+		})
+	}
+	runner.RunCmd(cmd)
+}


[incubator-devlake] 12/12: feat:add zentao bugs

Posted by wa...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

warren pushed a commit to branch feat-plugin-zentao
in repository https://gitbox.apache.org/repos/asf/incubator-devlake.git

commit 6a500469fbd5df7d2b5c8abbf6a6bc078c6ac272
Author: yuqiangabab <11...@qq.com>
AuthorDate: Wed Oct 5 12:57:25 2022 +0800

    feat:add zentao bugs
---
 plugins/zentao/impl/impl.go                        |   3 +
 plugins/zentao/models/archived/bug.go              | 112 +++++++++++++++++++++
 plugins/zentao/models/bug.go                       | 112 +++++++++++++++++++++
 .../migrationscripts/20220906_add_init_tables.go   |   1 +
 plugins/zentao/tasks/bug_collector.go              |  84 ++++++++++++++++
 plugins/zentao/tasks/bug_convertor.go              | 110 ++++++++++++++++++++
 plugins/zentao/tasks/bug_extractor.go              |  67 ++++++++++++
 7 files changed, 489 insertions(+)

diff --git a/plugins/zentao/impl/impl.go b/plugins/zentao/impl/impl.go
index aaf3c922..8091e898 100644
--- a/plugins/zentao/impl/impl.go
+++ b/plugins/zentao/impl/impl.go
@@ -60,6 +60,9 @@ func (plugin Zentao) SubTaskMetas() []core.SubTaskMeta {
 		tasks.CollectStoriesMeta,
 		tasks.ExtractStoriesMeta,
 		tasks.ConvertStoriesMeta,
+		tasks.CollectBugMeta,
+		tasks.ExtractBugMeta,
+		tasks.ConvertBugMeta,
 	}
 }
 
diff --git a/plugins/zentao/models/archived/bug.go b/plugins/zentao/models/archived/bug.go
new file mode 100644
index 00000000..3743b761
--- /dev/null
+++ b/plugins/zentao/models/archived/bug.go
@@ -0,0 +1,112 @@
+/*
+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 archived
+
+import (
+	"github.com/apache/incubator-devlake/models/migrationscripts/archived"
+	"github.com/apache/incubator-devlake/plugins/helper"
+)
+
+type ZentaoBug struct {
+	archived.NoPKModel
+	ConnectionId   uint64   `gorm:"primaryKey;type:BIGINT  NOT NULL"`
+	ID             int      `json:"id" gorm:"primaryKey"`
+	Project        int      `json:"project"`
+	Product        int      `json:"product"`
+	Injection      int      `json:"injection"`
+	Identify       int      `json:"identify"`
+	Branch         int      `json:"branch"`
+	Module         int      `json:"module"`
+	Execution      int      `json:"execution"`
+	Plan           int      `json:"plan"`
+	Story          int      `json:"story"`
+	StoryVersion   int      `json:"storyVersion"`
+	Task           int      `json:"task"`
+	ToTask         int      `json:"toTask"`
+	ToStory        int      `json:"toStory"`
+	Title          string   `json:"title"`
+	Keywords       string   `json:"keywords"`
+	Severity       int      `json:"severity"`
+	Pri            int      `json:"pri"`
+	Type           string   `json:"type"`
+	Os             string   `json:"os"`
+	Browser        string   `json:"browser"`
+	Hardware       string   `json:"hardware"`
+	Found          string   `json:"found"`
+	Steps          string   `json:"steps"`
+	Status         string   `json:"status"`
+	SubStatus      string   `json:"subStatus"`
+	Color          string   `json:"color"`
+	Confirmed      int      `json:"confirmed"`
+	ActivatedCount int      `json:"activatedCount"`
+	ActivatedDate  string   `json:"activatedDate"`
+	FeedbackBy     string   `json:"feedbackBy"`
+	NotifyEmail    string   `json:"notifyEmail"`
+	Mailto         []Mailto `json:"mailto" gorm:"-:all"`
+	OpenedBy       `json:"openedBy"`
+	OpenedDate     *helper.Iso8601Time `json:"openedDate"`
+	OpenedBuild    string              `json:"openedBuild"`
+	AssignedTo     `json:"assignedTo"`
+	AssignedDate   *helper.Iso8601Time `json:"assignedDate"`
+	Deadline       string              `json:"deadline"`
+	ResolvedBy     `json:"resolvedBy"`
+	Resolution     string              `json:"resolution"`
+	ResolvedBuild  string              `json:"resolvedBuild"`
+	ResolvedDate   *helper.Iso8601Time `json:"resolvedDate"`
+	ClosedBy       `json:"closedBy"`
+	ClosedDate     *helper.Iso8601Time `json:"closedDate"`
+	DuplicateBug   int                 `json:"duplicateBug"`
+	LinkBug        string              `json:"linkBug"`
+	Case           int                 `json:"case"`
+	CaseVersion    int                 `json:"caseVersion"`
+	Feedback       int                 `json:"feedback"`
+	Result         int                 `json:"result"`
+	Repo           int                 `json:"repo"`
+	Mr             int                 `json:"mr"`
+	Entry          string              `json:"entry"`
+	Lines          string              `json:"lines"`
+	V1             string              `json:"v1"`
+	V2             string              `json:"v2"`
+	RepoType       string              `json:"repoType"`
+	IssueKey       string              `json:"issueKey"`
+	Testtask       int                 `json:"testtask"`
+	LastEditedBy   `json:"lastEditedBy"`
+	LastEditedDate *helper.Iso8601Time `json:"lastEditedDate"`
+	Deleted        bool                `json:"deleted"`
+	PriOrder       string              `json:"priOrder"`
+	SeverityOrder  int                 `json:"severityOrder"`
+	Needconfirm    bool                `json:"needconfirm"`
+	StatusName     string              `json:"statusName"`
+	ProductStatus  string              `json:"productStatus"`
+}
+type ResolvedBy struct {
+	ResolvedByID       int    `json:"id"`
+	ResolvedByAccount  string `json:"account"`
+	ResolvedByAvatar   string `json:"avatar"`
+	ResolvedByRealname string `json:"realname"`
+}
+type Mailto struct {
+	MailtoID       int    `json:"id"`
+	MailtoAccount  string `json:"account"`
+	MailtoAvatar   string `json:"avatar"`
+	MailtoRealname string `json:"realname"`
+}
+
+func (ZentaoBug) TableName() string {
+	return "_tool_zentao_bugs"
+}
diff --git a/plugins/zentao/models/bug.go b/plugins/zentao/models/bug.go
new file mode 100644
index 00000000..c081ed9c
--- /dev/null
+++ b/plugins/zentao/models/bug.go
@@ -0,0 +1,112 @@
+/*
+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 models
+
+import (
+	"github.com/apache/incubator-devlake/models/common"
+	"github.com/apache/incubator-devlake/plugins/helper"
+)
+
+type ZentaoBug struct {
+	common.NoPKModel
+	ConnectionId   uint64   `gorm:"primaryKey;type:BIGINT  NOT NULL"`
+	ID             int      `json:"id" gorm:"primaryKey"`
+	Project        int      `json:"project"`
+	Product        int      `json:"product"`
+	Injection      int      `json:"injection"`
+	Identify       int      `json:"identify"`
+	Branch         int      `json:"branch"`
+	Module         int      `json:"module"`
+	Execution      int      `json:"execution"`
+	Plan           int      `json:"plan"`
+	Story          int      `json:"story"`
+	StoryVersion   int      `json:"storyVersion"`
+	Task           int      `json:"task"`
+	ToTask         int      `json:"toTask"`
+	ToStory        int      `json:"toStory"`
+	Title          string   `json:"title"`
+	Keywords       string   `json:"keywords"`
+	Severity       int      `json:"severity"`
+	Pri            int      `json:"pri"`
+	Type           string   `json:"type"`
+	Os             string   `json:"os"`
+	Browser        string   `json:"browser"`
+	Hardware       string   `json:"hardware"`
+	Found          string   `json:"found"`
+	Steps          string   `json:"steps"`
+	Status         string   `json:"status"`
+	SubStatus      string   `json:"subStatus"`
+	Color          string   `json:"color"`
+	Confirmed      int      `json:"confirmed"`
+	ActivatedCount int      `json:"activatedCount"`
+	ActivatedDate  string   `json:"activatedDate"`
+	FeedbackBy     string   `json:"feedbackBy"`
+	NotifyEmail    string   `json:"notifyEmail"`
+	Mailto         []Mailto `json:"mailto" gorm:"-:all"`
+	OpenedBy       `json:"openedBy"`
+	OpenedDate     *helper.Iso8601Time `json:"openedDate"`
+	OpenedBuild    string              `json:"openedBuild"`
+	AssignedTo     `json:"assignedTo"`
+	AssignedDate   *helper.Iso8601Time `json:"assignedDate"`
+	Deadline       string              `json:"deadline"`
+	ResolvedBy     `json:"resolvedBy"`
+	Resolution     string              `json:"resolution"`
+	ResolvedBuild  string              `json:"resolvedBuild"`
+	ResolvedDate   *helper.Iso8601Time `json:"resolvedDate"`
+	ClosedBy       `json:"closedBy"`
+	ClosedDate     *helper.Iso8601Time `json:"closedDate"`
+	DuplicateBug   int                 `json:"duplicateBug"`
+	LinkBug        string              `json:"linkBug"`
+	Case           int                 `json:"case"`
+	CaseVersion    int                 `json:"caseVersion"`
+	Feedback       int                 `json:"feedback"`
+	Result         int                 `json:"result"`
+	Repo           int                 `json:"repo"`
+	Mr             int                 `json:"mr"`
+	Entry          string              `json:"entry"`
+	Lines          string              `json:"lines"`
+	V1             string              `json:"v1"`
+	V2             string              `json:"v2"`
+	RepoType       string              `json:"repoType"`
+	IssueKey       string              `json:"issueKey"`
+	Testtask       int                 `json:"testtask"`
+	LastEditedBy   `json:"lastEditedBy"`
+	LastEditedDate *helper.Iso8601Time `json:"lastEditedDate"`
+	Deleted        bool                `json:"deleted"`
+	PriOrder       string              `json:"priOrder"`
+	SeverityOrder  int                 `json:"severityOrder"`
+	Needconfirm    bool                `json:"needconfirm"`
+	StatusName     string              `json:"statusName"`
+	ProductStatus  string              `json:"productStatus"`
+}
+type ResolvedBy struct {
+	ResolvedByID       int    `json:"id"`
+	ResolvedByAccount  string `json:"account"`
+	ResolvedByAvatar   string `json:"avatar"`
+	ResolvedByRealname string `json:"realname"`
+}
+type Mailto struct {
+	MailtoID       int    `json:"id"`
+	MailtoAccount  string `json:"account"`
+	MailtoAvatar   string `json:"avatar"`
+	MailtoRealname string `json:"realname"`
+}
+
+func (ZentaoBug) TableName() string {
+	return "_tool_zentao_bugs"
+}
diff --git a/plugins/zentao/models/migrationscripts/20220906_add_init_tables.go b/plugins/zentao/models/migrationscripts/20220906_add_init_tables.go
index 3d51dcde..ade33e29 100644
--- a/plugins/zentao/models/migrationscripts/20220906_add_init_tables.go
+++ b/plugins/zentao/models/migrationscripts/20220906_add_init_tables.go
@@ -31,6 +31,7 @@ func (u *addInitTables) Up(ctx context.Context, db *gorm.DB) error {
 		archived.ZentaoProject{},
 		archived.ZentaoExecution{},
 		archived.ZentaoStories{},
+		archived.ZentaoBug{},
 	)
 }
 
diff --git a/plugins/zentao/tasks/bug_collector.go b/plugins/zentao/tasks/bug_collector.go
new file mode 100644
index 00000000..d47c3bdd
--- /dev/null
+++ b/plugins/zentao/tasks/bug_collector.go
@@ -0,0 +1,84 @@
+/*
+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/plugins/core"
+	"github.com/apache/incubator-devlake/plugins/helper"
+	"io"
+	"net/http"
+	"net/url"
+)
+
+const RAW_BUG_TABLE = "zentao_bug"
+
+var _ core.SubTaskEntryPoint = CollectExecution
+
+func CollectBug(taskCtx core.SubTaskContext) error {
+	data := taskCtx.GetData().(*ZentaoTaskData)
+	collector, err := helper.NewApiCollector(helper.ApiCollectorArgs{
+		RawDataSubTaskArgs: helper.RawDataSubTaskArgs{
+			Ctx: taskCtx,
+			Params: ZentaoApiParams{
+				ProductId:   data.Options.ProductId,
+				ExecutionId: data.Options.ExecutionId,
+				ProjectId:   data.Options.ProjectId,
+			},
+			Table: RAW_BUG_TABLE,
+		},
+		ApiClient:   data.ApiClient,
+		Incremental: false,
+		PageSize:    100,
+		// TODO write which api would you want request
+		UrlTemplate: "/products/{{ .Params.ProductId }}/bugs",
+		Query: func(reqData *helper.RequestData) (url.Values, 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
+		},
+		GetTotalPages: GetTotalPagesFromResponse,
+		ResponseParser: func(res *http.Response) ([]json.RawMessage, error) {
+			var data struct {
+				Bugs []json.RawMessage `json:"bugs"`
+			}
+			body, err := io.ReadAll(res.Body)
+			json.Unmarshal(body, &data)
+			res.Body.Close()
+			if err != nil {
+				return nil, err
+			}
+			return data.Bugs, nil
+			//return []json.RawMessage{body}, nil
+		},
+	})
+	if err != nil {
+		return err
+	}
+
+	return collector.Execute()
+}
+
+var CollectBugMeta = core.SubTaskMeta{
+	Name:             "CollectBug",
+	EntryPoint:       CollectBug,
+	EnabledByDefault: true,
+	Description:      "Collect Bug data from Zentao api",
+}
diff --git a/plugins/zentao/tasks/bug_convertor.go b/plugins/zentao/tasks/bug_convertor.go
new file mode 100644
index 00000000..beb99cfd
--- /dev/null
+++ b/plugins/zentao/tasks/bug_convertor.go
@@ -0,0 +1,110 @@
+/*
+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 (
+	"github.com/apache/incubator-devlake/models/domainlayer"
+	"github.com/apache/incubator-devlake/models/domainlayer/didgen"
+	"github.com/apache/incubator-devlake/models/domainlayer/ticket"
+	"github.com/apache/incubator-devlake/plugins/core"
+	"github.com/apache/incubator-devlake/plugins/core/dal"
+	"github.com/apache/incubator-devlake/plugins/helper"
+	"github.com/apache/incubator-devlake/plugins/zentao/models"
+	"reflect"
+)
+
+var _ core.SubTaskEntryPoint = ConvertExecutions
+
+var ConvertBugMeta = core.SubTaskMeta{
+	Name:             "convertBug",
+	EntryPoint:       ConvertBug,
+	EnabledByDefault: true,
+	Description:      "convert Zentao bug",
+	DomainTypes:      []string{core.DOMAIN_TYPE_TICKET},
+}
+
+func ConvertBug(taskCtx core.SubTaskContext) error {
+	data := taskCtx.GetData().(*ZentaoTaskData)
+	db := taskCtx.GetDal()
+	boardIdGen := didgen.NewDomainIdGenerator(&models.ZentaoBug{})
+	cursor, err := db.Cursor(
+		dal.From(&models.ZentaoBug{}),
+		dal.Where(`_tool_zentao_bugs.product = ? and
+			_tool_zentao_bugs.connection_id = ?`, data.Options.ProductId, data.Options.ConnectionId),
+	)
+	if err != nil {
+		return err
+	}
+	defer cursor.Close()
+	convertor, err := helper.NewDataConverter(helper.DataConverterArgs{
+		InputRowType: reflect.TypeOf(models.ZentaoBug{}),
+		Input:        cursor,
+		RawDataSubTaskArgs: helper.RawDataSubTaskArgs{
+			Ctx: taskCtx,
+			Params: ZentaoApiParams{
+				ProductId:   data.Options.ProductId,
+				ExecutionId: data.Options.ExecutionId,
+				ProjectId:   data.Options.ProjectId,
+			},
+			Table: RAW_BUG_TABLE,
+		},
+		Convert: func(inputRow interface{}) ([]interface{}, error) {
+			toolBug := inputRow.(*models.ZentaoBug)
+			domainBoard := &ticket.Issue{
+				DomainEntity: domainlayer.DomainEntity{
+					Id: boardIdGen.Generate(toolBug.ConnectionId, toolBug.ID),
+				},
+				Url:                     "",
+				IconURL:                 "",
+				IssueKey:                toolBug.IssueKey,
+				Title:                   toolBug.Title,
+				Description:             "",
+				EpicKey:                 "",
+				Type:                    toolBug.Type,
+				Status:                  toolBug.Status,
+				OriginalStatus:          "",
+				StoryPoint:              0,
+				ResolutionDate:          toolBug.ResolvedDate.ToNullableTime(),
+				CreatedDate:             toolBug.OpenedDate.ToNullableTime(),
+				UpdatedDate:             toolBug.LastEditedDate.ToNullableTime(),
+				LeadTimeMinutes:         0,
+				ParentIssueId:           "",
+				Priority:                string(toolBug.Pri),
+				OriginalEstimateMinutes: 0,
+				TimeSpentMinutes:        0,
+				TimeRemainingMinutes:    0,
+				CreatorId:               string(toolBug.OpenedBy.OpenedByID),
+				CreatorName:             toolBug.OpenedBy.OpenedByRealname,
+				AssigneeId:              string(toolBug.AssignedTo.ID),
+				AssigneeName:            toolBug.AssignedTo.Realname,
+				Severity:                string(toolBug.Severity),
+				Component:               "",
+				DeploymentId:            "",
+			}
+			results := make([]interface{}, 0)
+			results = append(results, domainBoard)
+			return results, nil
+		},
+	})
+
+	if err != nil {
+		return err
+	}
+
+	return convertor.Execute()
+}
diff --git a/plugins/zentao/tasks/bug_extractor.go b/plugins/zentao/tasks/bug_extractor.go
new file mode 100644
index 00000000..4dbb3205
--- /dev/null
+++ b/plugins/zentao/tasks/bug_extractor.go
@@ -0,0 +1,67 @@
+/*
+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/plugins/core"
+	"github.com/apache/incubator-devlake/plugins/helper"
+	"github.com/apache/incubator-devlake/plugins/zentao/models"
+)
+
+var _ core.SubTaskEntryPoint = ExtractBug
+
+var ExtractBugMeta = core.SubTaskMeta{
+	Name:             "extractBug",
+	EntryPoint:       ExtractBug,
+	EnabledByDefault: true,
+	Description:      "extract Zentao bug",
+	DomainTypes:      []string{core.DOMAIN_TYPE_TICKET},
+}
+
+func ExtractBug(taskCtx core.SubTaskContext) error {
+	data := taskCtx.GetData().(*ZentaoTaskData)
+	extractor, err := helper.NewApiExtractor(helper.ApiExtractorArgs{
+		RawDataSubTaskArgs: helper.RawDataSubTaskArgs{
+			Ctx: taskCtx,
+			Params: ZentaoApiParams{
+				ProductId:   data.Options.ProductId,
+				ExecutionId: data.Options.ExecutionId,
+				ProjectId:   data.Options.ProjectId,
+			},
+			Table: RAW_BUG_TABLE,
+		},
+		Extract: func(row *helper.RawData) ([]interface{}, error) {
+			bug := &models.ZentaoBug{}
+			err := json.Unmarshal(row.Data, bug)
+			if err != nil {
+				return nil, err
+			}
+			bug.ConnectionId = data.Options.ConnectionId
+			results := make([]interface{}, 0)
+			results = append(results, bug)
+			return results, nil
+		},
+	})
+
+	if err != nil {
+		return err
+	}
+
+	return extractor.Execute()
+}


[incubator-devlake] 04/12: update project collector

Posted by wa...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

warren pushed a commit to branch feat-plugin-zentao
in repository https://gitbox.apache.org/repos/asf/incubator-devlake.git

commit ef1c75fb7ec98d0e958ea19a24ca2c90cac49b1b
Author: Yingchu Chen <yi...@merico.dev>
AuthorDate: Sat Sep 10 15:50:00 2022 +0800

    update project collector
---
 plugins/zentao/tasks/api_client.go        |  4 ++++
 plugins/zentao/tasks/project_collector.go | 11 +++-------
 plugins/zentao/tasks/shared.go            | 34 +++++++++++++++++++++++++++++++
 3 files changed, 41 insertions(+), 8 deletions(-)

diff --git a/plugins/zentao/tasks/api_client.go b/plugins/zentao/tasks/api_client.go
index 17d490fb..e5105ded 100644
--- a/plugins/zentao/tasks/api_client.go
+++ b/plugins/zentao/tasks/api_client.go
@@ -89,3 +89,7 @@ func NewZentaoApiClient(taskCtx core.TaskContext, connection *models.ZentaoConne
 	}
 	return asyncApiClient, nil
 }
+
+type ZentaoPagination struct {
+	Page int `json:"page"`
+}
diff --git a/plugins/zentao/tasks/project_collector.go b/plugins/zentao/tasks/project_collector.go
index 7af9dc2c..1aafec69 100644
--- a/plugins/zentao/tasks/project_collector.go
+++ b/plugins/zentao/tasks/project_collector.go
@@ -32,11 +32,6 @@ var _ core.SubTaskEntryPoint = CollectProject
 
 func CollectProject(taskCtx core.SubTaskContext) error {
 	data := taskCtx.GetData().(*ZentaoTaskData)
-	iterator, err := helper.NewDateIterator(365)
-	if err != nil {
-		return err
-	}
-
 	collector, err := helper.NewApiCollector(helper.ApiCollectorArgs{
 		RawDataSubTaskArgs: helper.RawDataSubTaskArgs{
 			Ctx:    taskCtx,
@@ -45,7 +40,6 @@ func CollectProject(taskCtx core.SubTaskContext) error {
 		},
 		ApiClient:   data.ApiClient,
 		Incremental: false,
-		Input:       iterator,
 		PageSize:    100,
 		// TODO write which api would you want request
 		UrlTemplate: "projects",
@@ -55,11 +49,12 @@ func CollectProject(taskCtx core.SubTaskContext) error {
 			query.Set("limit", fmt.Sprintf("%v", reqData.Pager.Size))
 			return query, nil
 		},
+		GetTotalPages: GetTotalPagesFromResponse,
 		ResponseParser: func(res *http.Response) ([]json.RawMessage, error) {
 			var data struct {
-				Projects []json.RawMessage `json:"data"`
+				Projects []json.RawMessage `json:"projects"`
 			}
-			err = helper.UnmarshalResponse(res, &data)
+			err := helper.UnmarshalResponse(res, &data)
 			return data.Projects, err
 		},
 	})
diff --git a/plugins/zentao/tasks/shared.go b/plugins/zentao/tasks/shared.go
new file mode 100644
index 00000000..503e1f54
--- /dev/null
+++ b/plugins/zentao/tasks/shared.go
@@ -0,0 +1,34 @@
+/*
+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 (
+	"net/http"
+
+	"github.com/apache/incubator-devlake/plugins/helper"
+)
+
+func GetTotalPagesFromResponse(res *http.Response, args *helper.ApiCollectorArgs) (int, error) {
+	body := &ZentaoPagination{}
+	err := helper.UnmarshalResponse(res, body)
+	if err != nil {
+		return 0, err
+	}
+	return body.Page, nil
+
+}


[incubator-devlake] 07/12: feat(zentao): add execution

Posted by wa...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

warren pushed a commit to branch feat-plugin-zentao
in repository https://gitbox.apache.org/repos/asf/incubator-devlake.git

commit 47f3ecd6f1e500fa84a2aa8b6b818e891a236d07
Author: Yingchu Chen <yi...@merico.dev>
AuthorDate: Mon Sep 19 10:38:53 2022 +0800

    feat(zentao): add execution
---
 plugins/zentao/impl/impl.go                        |   2 +
 plugins/zentao/models/archived/execution.go        | 151 +++++++++++++++++++++
 plugins/zentao/models/execution.go                 | 151 +++++++++++++++++++++
 .../migrationscripts/20220906_add_init_tables.go   |   5 +-
 plugins/zentao/tasks/execution_collector.go        |  79 +++++++++++
 plugins/zentao/tasks/execution_extractor.go        |  67 +++++++++
 6 files changed, 453 insertions(+), 2 deletions(-)

diff --git a/plugins/zentao/impl/impl.go b/plugins/zentao/impl/impl.go
index f24f6afe..920bced0 100644
--- a/plugins/zentao/impl/impl.go
+++ b/plugins/zentao/impl/impl.go
@@ -54,6 +54,8 @@ func (plugin Zentao) SubTaskMetas() []core.SubTaskMeta {
 	return []core.SubTaskMeta{
 		tasks.CollectProjectMeta,
 		tasks.ExtractProjectsMeta,
+		tasks.CollectExecutionMeta,
+		tasks.ExtractExecutionsMeta,
 	}
 }
 
diff --git a/plugins/zentao/models/archived/execution.go b/plugins/zentao/models/archived/execution.go
new file mode 100644
index 00000000..787527c1
--- /dev/null
+++ b/plugins/zentao/models/archived/execution.go
@@ -0,0 +1,151 @@
+package archived
+
+import (
+	"github.com/apache/incubator-devlake/models/migrationscripts/archived"
+	"time"
+)
+
+type ZentaoExecution struct {
+	ConnectionId   uint64 `gorm:"primaryKey"`
+	Id             uint64 `json:"id"`
+	Project        uint64 `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         int    `json:"parent"`
+	Path           string `json:"path"`
+	Grade          int    `json:"grade"`
+	Name           string `json:"name"`
+	Code           string `json:"code"`
+	Begin          string `json:"begin"`
+	End            string `json:"end"`
+	RealBegan      string `json:"realBegan"`
+	RealEnd        string `json:"realEnd"`
+	Days           int    `json:"days"`
+	Status         string `json:"status"`
+	SubStatus      string `json:"subStatus"`
+	Pri            string `json:"pri"`
+	Desc           string `json:"desc"`
+	Version        int    `json:"version"`
+	ParentVersion  int    `json:"parentVersion"`
+	PlanDuration   int    `json:"planDuration"`
+	RealDuration   int    `json:"realDuration"`
+	OpenedBy       `json:"openedBy"`
+	OpenedDate     time.Time `json:"openedDate"`
+	OpenedVersion  string    `json:"openedVersion"`
+	LastEditedBy   `json:"lastEditedBy"`
+	LastEditedDate time.Time `json:"lastEditedDate"`
+	ClosedBy       `json:"closedBy"`
+	ClosedDate     time.Time `json:"closedDate"`
+	CanceledBy     `json:"canceledBy"`
+	CanceledDate   time.Time `json:"canceledDate"`
+	SuspendedDate  string    `json:"suspendedDate"`
+	PO             `json:"PO"`
+	PM             `json:"PM"`
+	QD             `json:"QD"`
+	RD             `json:"RD"`
+	Team           string `json:"team"`
+	Acl            string `json:"acl"`
+	//Whitelist      []Whitelist  `json:"whitelist" gorm:"-:all"`
+	Order         int          `json:"order"`
+	Vision        string       `json:"vision"`
+	DisplayCards  int          `json:"displayCards"`
+	FluidBoard    string       `json:"fluidBoard"`
+	Deleted       bool         `json:"deleted"`
+	TotalHours    int          `json:"totalHours"`
+	TotalEstimate int          `json:"totalEstimate"`
+	TotalConsumed int          `json:"totalConsumed"`
+	TotalLeft     int          `json:"totalLeft"`
+	ProjectInfo   bool         `json:"projectInfo"`
+	Progress      int          `json:"progress"`
+	TeamMembers   []TeamMember `json:"teamMembers" gorm:"-:all"`
+	Products      []Product    `json:"products" gorm:"-:all"`
+	CaseReview    bool         `json:"caseReview"`
+	archived.NoPKModel
+}
+
+type OpenedBy struct {
+	OpenedByID       int    `json:"id"`
+	OpenedByAccount  string `json:"account"`
+	OpenedByAvatar   string `json:"avatar"`
+	OpenedByRealname string `json:"realname"`
+}
+
+type LastEditedBy struct {
+	LastEditedByID       int    `json:"id"`
+	LastEditedByAccount  string `json:"account"`
+	LastEditedByAvatar   string `json:"avatar"`
+	LastEditedByRealname string `json:"realname"`
+}
+
+type ClosedBy struct {
+	ClosedByID       int    `json:"id"`
+	ClosedByAccount  string `json:"account"`
+	ClosedByAvatar   string `json:"avatar"`
+	ClosedByRealname string `json:"realname"`
+}
+
+type CanceledBy struct {
+	CanceledByID       int    `json:"id"`
+	CanceledByAccount  string `json:"account"`
+	CanceledByAvatar   string `json:"avatar"`
+	CanceledByRealname string `json:"realname"`
+}
+
+type PO struct {
+	PoID       int    `json:"id"`
+	PoAccount  string `json:"account"`
+	PoAvatar   string `json:"avatar"`
+	PoRealname string `json:"realname"`
+}
+
+type QD struct {
+	ID       int    `json:"id"`
+	Account  string `json:"account"`
+	Avatar   string `json:"avatar"`
+	Realname string `json:"realname"`
+}
+
+type RD struct {
+	ID       int    `json:"id"`
+	Account  string `json:"account"`
+	Avatar   string `json:"avatar"`
+	Realname string `json:"realname"`
+}
+
+type Product struct {
+	ID    int           `json:"id"`
+	Name  string        `json:"name"`
+	Plans []interface{} `json:"plans"`
+}
+
+type TeamMember struct {
+	ID         int    `json:"id"`
+	Root       int    `json:"root"`
+	Type       string `json:"type"`
+	Account    string `json:"account"`
+	Role       string `json:"role"`
+	Position   string `json:"position"`
+	Limited    string `json:"limited"`
+	Join       string `json:"join"`
+	Days       int    `json:"days"`
+	Hours      int    `json:"hours"`
+	Estimate   string `json:"estimate"`
+	Consumed   string `json:"consumed"`
+	Left       string `json:"left"`
+	Order      int    `json:"order"`
+	TotalHours int    `json:"totalHours"`
+	UserID     int    `json:"userID"`
+	Realname   string `json:"realname"`
+}
+
+func (ZentaoExecution) TableName() string {
+	return "_tool_zentao_execution"
+}
diff --git a/plugins/zentao/models/execution.go b/plugins/zentao/models/execution.go
new file mode 100644
index 00000000..e51102cf
--- /dev/null
+++ b/plugins/zentao/models/execution.go
@@ -0,0 +1,151 @@
+package models
+
+import (
+	"github.com/apache/incubator-devlake/models/common"
+	"time"
+)
+
+type ZentaoExecution struct {
+	ConnectionId   uint64 `gorm:"primaryKey"`
+	Id             uint64 `json:"id"`
+	Project        uint64 `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         int    `json:"parent"`
+	Path           string `json:"path"`
+	Grade          int    `json:"grade"`
+	Name           string `json:"name"`
+	Code           string `json:"code"`
+	Begin          string `json:"begin"`
+	End            string `json:"end"`
+	RealBegan      string `json:"realBegan"`
+	RealEnd        string `json:"realEnd"`
+	Days           int    `json:"days"`
+	Status         string `json:"status"`
+	SubStatus      string `json:"subStatus"`
+	Pri            string `json:"pri"`
+	Desc           string `json:"desc"`
+	Version        int    `json:"version"`
+	ParentVersion  int    `json:"parentVersion"`
+	PlanDuration   int    `json:"planDuration"`
+	RealDuration   int    `json:"realDuration"`
+	OpenedBy       `json:"openedBy"`
+	OpenedDate     time.Time `json:"openedDate"`
+	OpenedVersion  string    `json:"openedVersion"`
+	LastEditedBy   `json:"lastEditedBy"`
+	LastEditedDate time.Time `json:"lastEditedDate"`
+	ClosedBy       `json:"closedBy"`
+	ClosedDate     time.Time `json:"closedDate"`
+	CanceledBy     `json:"canceledBy"`
+	CanceledDate   time.Time `json:"canceledDate"`
+	SuspendedDate  string    `json:"suspendedDate"`
+	PO             `json:"PO"`
+	PM             `json:"PM"`
+	QD             `json:"QD"`
+	RD             `json:"RD"`
+	Team           string `json:"team"`
+	Acl            string `json:"acl"`
+	//Whitelist      []Whitelist  `json:"whitelist" gorm:"-:all"`
+	Order         int          `json:"order"`
+	Vision        string       `json:"vision"`
+	DisplayCards  int          `json:"displayCards"`
+	FluidBoard    string       `json:"fluidBoard"`
+	Deleted       bool         `json:"deleted"`
+	TotalHours    int          `json:"totalHours"`
+	TotalEstimate int          `json:"totalEstimate"`
+	TotalConsumed int          `json:"totalConsumed"`
+	TotalLeft     int          `json:"totalLeft"`
+	ProjectInfo   bool         `json:"projectInfo"`
+	Progress      int          `json:"progress"`
+	TeamMembers   []TeamMember `json:"teamMembers" gorm:"-:all"`
+	Products      []Product    `json:"products" gorm:"-:all"`
+	CaseReview    bool         `json:"caseReview"`
+	common.NoPKModel
+}
+
+func (ZentaoExecution) TableName() string {
+	return "_tool_zentao_execution"
+}
+
+type OpenedBy struct {
+	OpenedByID       int    `json:"id"`
+	OpenedByAccount  string `json:"account"`
+	OpenedByAvatar   string `json:"avatar"`
+	OpenedByRealname string `json:"realname"`
+}
+
+type LastEditedBy struct {
+	LastEditedByID       int    `json:"id"`
+	LastEditedByAccount  string `json:"account"`
+	LastEditedByAvatar   string `json:"avatar"`
+	LastEditedByRealname string `json:"realname"`
+}
+
+type ClosedBy struct {
+	ClosedByID       int    `json:"id"`
+	ClosedByAccount  string `json:"account"`
+	ClosedByAvatar   string `json:"avatar"`
+	ClosedByRealname string `json:"realname"`
+}
+
+type CanceledBy struct {
+	CanceledByID       int    `json:"id"`
+	CanceledByAccount  string `json:"account"`
+	CanceledByAvatar   string `json:"avatar"`
+	CanceledByRealname string `json:"realname"`
+}
+
+type PO struct {
+	PoID       int    `json:"id"`
+	PoAccount  string `json:"account"`
+	PoAvatar   string `json:"avatar"`
+	PoRealname string `json:"realname"`
+}
+
+type QD struct {
+	ID       int    `json:"id"`
+	Account  string `json:"account"`
+	Avatar   string `json:"avatar"`
+	Realname string `json:"realname"`
+}
+
+type RD struct {
+	ID       int    `json:"id"`
+	Account  string `json:"account"`
+	Avatar   string `json:"avatar"`
+	Realname string `json:"realname"`
+}
+
+type Product struct {
+	ID    int           `json:"id"`
+	Name  string        `json:"name"`
+	Plans []interface{} `json:"plans"`
+}
+
+type TeamMember struct {
+	ID         int    `json:"id"`
+	Root       int    `json:"root"`
+	Type       string `json:"type"`
+	Account    string `json:"account"`
+	Role       string `json:"role"`
+	Position   string `json:"position"`
+	Limited    string `json:"limited"`
+	Join       string `json:"join"`
+	Days       int    `json:"days"`
+	Hours      int    `json:"hours"`
+	Estimate   string `json:"estimate"`
+	Consumed   string `json:"consumed"`
+	Left       string `json:"left"`
+	Order      int    `json:"order"`
+	TotalHours int    `json:"totalHours"`
+	UserID     int    `json:"userID"`
+	Realname   string `json:"realname"`
+}
diff --git a/plugins/zentao/models/migrationscripts/20220906_add_init_tables.go b/plugins/zentao/models/migrationscripts/20220906_add_init_tables.go
index 1a743c46..6635dda3 100644
--- a/plugins/zentao/models/migrationscripts/20220906_add_init_tables.go
+++ b/plugins/zentao/models/migrationscripts/20220906_add_init_tables.go
@@ -27,8 +27,9 @@ type addInitTables struct{}
 
 func (u *addInitTables) Up(ctx context.Context, db *gorm.DB) error {
 	return db.Migrator().AutoMigrate(
-		archived.ZentaoConnection{},
-		archived.ZentaoProject{},
+		//archived.ZentaoConnection{},
+		//archived.ZentaoProject{},
+		archived.ZentaoExecution{},
 	)
 }
 
diff --git a/plugins/zentao/tasks/execution_collector.go b/plugins/zentao/tasks/execution_collector.go
new file mode 100644
index 00000000..b012084f
--- /dev/null
+++ b/plugins/zentao/tasks/execution_collector.go
@@ -0,0 +1,79 @@
+/*
+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/plugins/core"
+	"github.com/apache/incubator-devlake/plugins/helper"
+	"io"
+	"net/http"
+	"net/url"
+)
+
+const RAW_EXECUTION_TABLE = "zentao_execution"
+
+var _ core.SubTaskEntryPoint = CollectExecution
+
+func CollectExecution(taskCtx core.SubTaskContext) error {
+	data := taskCtx.GetData().(*ZentaoTaskData)
+	collector, err := helper.NewApiCollector(helper.ApiCollectorArgs{
+		RawDataSubTaskArgs: helper.RawDataSubTaskArgs{
+			Ctx: taskCtx,
+			Params: ZentaoApiParams{
+				ProductId:   data.Options.ProductId,
+				ExecutionId: data.Options.ExecutionId,
+				ProjectId:   data.Options.ProjectId,
+			},
+			Table: RAW_EXECUTION_TABLE,
+		},
+		ApiClient:   data.ApiClient,
+		Incremental: false,
+		PageSize:    100,
+		// TODO write which api would you want request
+		UrlTemplate: "executions/{{ .Params.ExecutionId }}",
+		Query: func(reqData *helper.RequestData) (url.Values, 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
+		},
+		GetTotalPages: GetTotalPagesFromResponse,
+		ResponseParser: func(res *http.Response) ([]json.RawMessage, error) {
+			body, err := io.ReadAll(res.Body)
+			res.Body.Close()
+			if err != nil {
+				return nil, err
+			}
+			return []json.RawMessage{body}, nil
+		},
+	})
+	if err != nil {
+		return err
+	}
+
+	return collector.Execute()
+}
+
+var CollectExecutionMeta = core.SubTaskMeta{
+	Name:             "CollectExecution",
+	EntryPoint:       CollectExecution,
+	EnabledByDefault: true,
+	Description:      "Collect Execution data from Zentao api",
+}
diff --git a/plugins/zentao/tasks/execution_extractor.go b/plugins/zentao/tasks/execution_extractor.go
new file mode 100644
index 00000000..f8b42226
--- /dev/null
+++ b/plugins/zentao/tasks/execution_extractor.go
@@ -0,0 +1,67 @@
+/*
+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/plugins/core"
+	"github.com/apache/incubator-devlake/plugins/helper"
+	"github.com/apache/incubator-devlake/plugins/zentao/models"
+)
+
+var _ core.SubTaskEntryPoint = ExtractExecutions
+
+var ExtractExecutionsMeta = core.SubTaskMeta{
+	Name:             "extractExecutions",
+	EntryPoint:       ExtractExecutions,
+	EnabledByDefault: true,
+	Description:      "extract Zentao executions",
+	DomainTypes:      []string{core.DOMAIN_TYPE_TICKET},
+}
+
+func ExtractExecutions(taskCtx core.SubTaskContext) error {
+	data := taskCtx.GetData().(*ZentaoTaskData)
+	extractor, err := helper.NewApiExtractor(helper.ApiExtractorArgs{
+		RawDataSubTaskArgs: helper.RawDataSubTaskArgs{
+			Ctx: taskCtx,
+			Params: ZentaoApiParams{
+				ProductId:   data.Options.ProductId,
+				ExecutionId: data.Options.ExecutionId,
+				ProjectId:   data.Options.ProjectId,
+			},
+			Table: RAW_EXECUTION_TABLE,
+		},
+		Extract: func(row *helper.RawData) ([]interface{}, error) {
+			execution := &models.ZentaoExecution{}
+			err := json.Unmarshal(row.Data, execution)
+			if err != nil {
+				return nil, err
+			}
+			execution.ConnectionId = data.Options.ConnectionId
+			results := make([]interface{}, 0)
+			results = append(results, execution)
+			return results, nil
+		},
+	})
+
+	if err != nil {
+		return err
+	}
+
+	return extractor.Execute()
+}


[incubator-devlake] 08/12: feat(zentao): fix execution time

Posted by wa...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

warren pushed a commit to branch feat-plugin-zentao
in repository https://gitbox.apache.org/repos/asf/incubator-devlake.git

commit 9919d22f29f03cbd6dbc4420039eb07899e73335
Author: Yingchu Chen <yi...@merico.dev>
AuthorDate: Mon Sep 19 11:05:59 2022 +0800

    feat(zentao): fix execution time
---
 plugins/helper/iso8601time.go               | 31 ++++++++++
 plugins/zentao/impl/impl.go                 |  1 +
 plugins/zentao/models/archived/execution.go | 78 ++++++++++++-------------
 plugins/zentao/models/archived/project.go   |  4 +-
 plugins/zentao/models/execution.go          | 78 ++++++++++++-------------
 plugins/zentao/models/project.go            |  4 +-
 plugins/zentao/tasks/execution_convertor.go | 91 +++++++++++++++++++++++++++++
 7 files changed, 205 insertions(+), 82 deletions(-)

diff --git a/plugins/helper/iso8601time.go b/plugins/helper/iso8601time.go
index 8d409262..3029adb1 100644
--- a/plugins/helper/iso8601time.go
+++ b/plugins/helper/iso8601time.go
@@ -18,6 +18,7 @@ limitations under the License.
 package helper
 
 import (
+	"database/sql/driver"
 	"fmt"
 	"regexp"
 	"strings"
@@ -63,6 +64,10 @@ func init() {
 			Matcher: regexp.MustCompile(`[+-][\d]{2}:[\d]{2}$`),
 			Format:  "2006-01-02T15:04:05-07:00",
 		},
+		{
+			Matcher: regexp.MustCompile(`[+-][\d]{2}-[\d]{2}$`),
+			Format:  "2006-01-02",
+		},
 	}
 }
 
@@ -131,3 +136,29 @@ func Iso8601TimeToTime(iso8601Time *Iso8601Time) *time.Time {
 	t := iso8601Time.ToTime()
 	return &t
 }
+
+// Value FIXME ...
+func (jt *Iso8601Time) Value() (driver.Value, error) {
+	if jt == nil {
+		return nil, nil
+	}
+	var zeroTime time.Time
+	t := jt.time
+	if t.UnixNano() == zeroTime.UnixNano() {
+		return nil, nil
+	}
+	return t, nil
+}
+
+// Scan FIXME ...
+func (jt *Iso8601Time) Scan(v interface{}) error {
+	value, ok := v.(time.Time)
+	if ok {
+		*jt = Iso8601Time{
+			time:   value,
+			format: time.RFC3339,
+		}
+		return nil
+	}
+	return fmt.Errorf("can not convert %v to timestamp", v)
+}
diff --git a/plugins/zentao/impl/impl.go b/plugins/zentao/impl/impl.go
index 920bced0..b4004753 100644
--- a/plugins/zentao/impl/impl.go
+++ b/plugins/zentao/impl/impl.go
@@ -56,6 +56,7 @@ func (plugin Zentao) SubTaskMetas() []core.SubTaskMeta {
 		tasks.ExtractProjectsMeta,
 		tasks.CollectExecutionMeta,
 		tasks.ExtractExecutionsMeta,
+		tasks.ConvertExecutionsMeta,
 	}
 }
 
diff --git a/plugins/zentao/models/archived/execution.go b/plugins/zentao/models/archived/execution.go
index 787527c1..7fe6cd16 100644
--- a/plugins/zentao/models/archived/execution.go
+++ b/plugins/zentao/models/archived/execution.go
@@ -2,51 +2,51 @@ package archived
 
 import (
 	"github.com/apache/incubator-devlake/models/migrationscripts/archived"
-	"time"
+	"github.com/apache/incubator-devlake/plugins/helper"
 )
 
 type ZentaoExecution struct {
-	ConnectionId   uint64 `gorm:"primaryKey"`
-	Id             uint64 `json:"id"`
-	Project        uint64 `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         int    `json:"parent"`
-	Path           string `json:"path"`
-	Grade          int    `json:"grade"`
-	Name           string `json:"name"`
-	Code           string `json:"code"`
-	Begin          string `json:"begin"`
-	End            string `json:"end"`
-	RealBegan      string `json:"realBegan"`
-	RealEnd        string `json:"realEnd"`
-	Days           int    `json:"days"`
-	Status         string `json:"status"`
-	SubStatus      string `json:"subStatus"`
-	Pri            string `json:"pri"`
-	Desc           string `json:"desc"`
-	Version        int    `json:"version"`
-	ParentVersion  int    `json:"parentVersion"`
-	PlanDuration   int    `json:"planDuration"`
-	RealDuration   int    `json:"realDuration"`
+	ConnectionId   uint64              `gorm:"primaryKey"`
+	Id             uint64              `json:"id" gorm:"primaryKey"`
+	Project        uint64              `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         int                 `json:"parent"`
+	Path           string              `json:"path"`
+	Grade          int                 `json:"grade"`
+	Name           string              `json:"name"`
+	Code           string              `json:"code"`
+	Begin          *helper.Iso8601Time `json:"begin"`
+	End            *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"`
+	Desc           string              `json:"desc"`
+	Version        int                 `json:"version"`
+	ParentVersion  int                 `json:"parentVersion"`
+	PlanDuration   int                 `json:"planDuration"`
+	RealDuration   int                 `json:"realDuration"`
 	OpenedBy       `json:"openedBy"`
-	OpenedDate     time.Time `json:"openedDate"`
-	OpenedVersion  string    `json:"openedVersion"`
+	OpenedDate     *helper.Iso8601Time `json:"openedDate"`
+	OpenedVersion  string              `json:"openedVersion"`
 	LastEditedBy   `json:"lastEditedBy"`
-	LastEditedDate time.Time `json:"lastEditedDate"`
+	LastEditedDate *helper.Iso8601Time `json:"lastEditedDate"`
 	ClosedBy       `json:"closedBy"`
-	ClosedDate     time.Time `json:"closedDate"`
+	ClosedDate     *helper.Iso8601Time `json:"closedDate"`
 	CanceledBy     `json:"canceledBy"`
-	CanceledDate   time.Time `json:"canceledDate"`
-	SuspendedDate  string    `json:"suspendedDate"`
+	CanceledDate   *helper.Iso8601Time `json:"canceledDate"`
+	SuspendedDate  *helper.Iso8601Time `json:"suspendedDate"`
 	PO             `json:"PO"`
 	PM             `json:"PM"`
 	QD             `json:"QD"`
@@ -147,5 +147,5 @@ type TeamMember struct {
 }
 
 func (ZentaoExecution) TableName() string {
-	return "_tool_zentao_execution"
+	return "_tool_zentao_executions"
 }
diff --git a/plugins/zentao/models/archived/project.go b/plugins/zentao/models/archived/project.go
index 63c498dd..94fcdfcb 100644
--- a/plugins/zentao/models/archived/project.go
+++ b/plugins/zentao/models/archived/project.go
@@ -25,7 +25,7 @@ import (
 type ZentaoProject struct {
 	archived.NoPKModel
 	ConnectionId  uint64 `gorm:"primaryKey;type:BIGINT  NOT NULL"`
-	ID            int    `json:"id"`
+	ID            int    `json:"id" gorm:"primaryKey;type:BIGINT  NOT NULL"`
 	Project       int    `json:"project"`
 	Model         string `json:"model"`
 	Type          string `json:"type"`
@@ -109,5 +109,5 @@ type Hours struct {
 }
 
 func (ZentaoProject) TableName() string {
-	return "_tool_zentao_project"
+	return "_tool_zentao_projects"
 }
diff --git a/plugins/zentao/models/execution.go b/plugins/zentao/models/execution.go
index e51102cf..054f21d7 100644
--- a/plugins/zentao/models/execution.go
+++ b/plugins/zentao/models/execution.go
@@ -2,51 +2,51 @@ package models
 
 import (
 	"github.com/apache/incubator-devlake/models/common"
-	"time"
+	"github.com/apache/incubator-devlake/plugins/helper"
 )
 
 type ZentaoExecution struct {
-	ConnectionId   uint64 `gorm:"primaryKey"`
-	Id             uint64 `json:"id"`
-	Project        uint64 `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         int    `json:"parent"`
-	Path           string `json:"path"`
-	Grade          int    `json:"grade"`
-	Name           string `json:"name"`
-	Code           string `json:"code"`
-	Begin          string `json:"begin"`
-	End            string `json:"end"`
-	RealBegan      string `json:"realBegan"`
-	RealEnd        string `json:"realEnd"`
-	Days           int    `json:"days"`
-	Status         string `json:"status"`
-	SubStatus      string `json:"subStatus"`
-	Pri            string `json:"pri"`
-	Desc           string `json:"desc"`
-	Version        int    `json:"version"`
-	ParentVersion  int    `json:"parentVersion"`
-	PlanDuration   int    `json:"planDuration"`
-	RealDuration   int    `json:"realDuration"`
+	ConnectionId   uint64              `gorm:"primaryKey"`
+	Id             uint64              `json:"id" gorm:"primaryKey"`
+	Project        uint64              `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         int                 `json:"parent"`
+	Path           string              `json:"path"`
+	Grade          int                 `json:"grade"`
+	Name           string              `json:"name"`
+	Code           string              `json:"code"`
+	Begin          *helper.Iso8601Time `json:"begin"`
+	End            *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"`
+	Desc           string              `json:"desc"`
+	Version        int                 `json:"version"`
+	ParentVersion  int                 `json:"parentVersion"`
+	PlanDuration   int                 `json:"planDuration"`
+	RealDuration   int                 `json:"realDuration"`
 	OpenedBy       `json:"openedBy"`
-	OpenedDate     time.Time `json:"openedDate"`
-	OpenedVersion  string    `json:"openedVersion"`
+	OpenedDate     *helper.Iso8601Time `json:"openedDate"`
+	OpenedVersion  string              `json:"openedVersion"`
 	LastEditedBy   `json:"lastEditedBy"`
-	LastEditedDate time.Time `json:"lastEditedDate"`
+	LastEditedDate *helper.Iso8601Time `json:"lastEditedDate"`
 	ClosedBy       `json:"closedBy"`
-	ClosedDate     time.Time `json:"closedDate"`
+	ClosedDate     *helper.Iso8601Time `json:"closedDate"`
 	CanceledBy     `json:"canceledBy"`
-	CanceledDate   time.Time `json:"canceledDate"`
-	SuspendedDate  string    `json:"suspendedDate"`
+	CanceledDate   *helper.Iso8601Time `json:"canceledDate"`
+	SuspendedDate  *helper.Iso8601Time `json:"suspendedDate"`
 	PO             `json:"PO"`
 	PM             `json:"PM"`
 	QD             `json:"QD"`
@@ -72,7 +72,7 @@ type ZentaoExecution struct {
 }
 
 func (ZentaoExecution) TableName() string {
-	return "_tool_zentao_execution"
+	return "_tool_zentao_executions"
 }
 
 type OpenedBy struct {
diff --git a/plugins/zentao/models/project.go b/plugins/zentao/models/project.go
index 5a51d738..db9450bf 100644
--- a/plugins/zentao/models/project.go
+++ b/plugins/zentao/models/project.go
@@ -25,7 +25,7 @@ import (
 type ZentaoProject struct {
 	common.NoPKModel
 	ConnectionId  uint64 `gorm:"primaryKey;type:BIGINT  NOT NULL"`
-	ID            int    `json:"id"`
+	ID            int    `json:"id" gorm:"primaryKey;type:BIGINT  NOT NULL"`
 	Project       int    `json:"project"`
 	Model         string `json:"model"`
 	Type          string `json:"type"`
@@ -109,5 +109,5 @@ type Hours struct {
 }
 
 func (ZentaoProject) TableName() string {
-	return "_tool_zentao_project"
+	return "_tool_zentao_projects"
 }
diff --git a/plugins/zentao/tasks/execution_convertor.go b/plugins/zentao/tasks/execution_convertor.go
new file mode 100644
index 00000000..727ba92d
--- /dev/null
+++ b/plugins/zentao/tasks/execution_convertor.go
@@ -0,0 +1,91 @@
+/*
+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 (
+	"github.com/apache/incubator-devlake/models/domainlayer"
+	"github.com/apache/incubator-devlake/models/domainlayer/didgen"
+	"github.com/apache/incubator-devlake/models/domainlayer/ticket"
+	"github.com/apache/incubator-devlake/plugins/core"
+	"github.com/apache/incubator-devlake/plugins/core/dal"
+	"github.com/apache/incubator-devlake/plugins/helper"
+	"github.com/apache/incubator-devlake/plugins/zentao/models"
+	"reflect"
+)
+
+var _ core.SubTaskEntryPoint = ConvertExecutions
+
+var ConvertExecutionsMeta = core.SubTaskMeta{
+	Name:             "convertExecutions",
+	EntryPoint:       ConvertExecutions,
+	EnabledByDefault: true,
+	Description:      "convert Zentao executions",
+	DomainTypes:      []string{core.DOMAIN_TYPE_TICKET},
+}
+
+func ConvertExecutions(taskCtx core.SubTaskContext) error {
+	data := taskCtx.GetData().(*ZentaoTaskData)
+	db := taskCtx.GetDal()
+	boardIdGen := didgen.NewDomainIdGenerator(&models.ZentaoExecution{})
+	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),
+	)
+	if err != nil {
+		return err
+	}
+	defer cursor.Close()
+	convertor, err := helper.NewDataConverter(helper.DataConverterArgs{
+		InputRowType: reflect.TypeOf(models.ZentaoExecution{}),
+		Input:        cursor,
+		RawDataSubTaskArgs: helper.RawDataSubTaskArgs{
+			Ctx: taskCtx,
+			Params: ZentaoApiParams{
+				ProductId:   data.Options.ProductId,
+				ExecutionId: data.Options.ExecutionId,
+				ProjectId:   data.Options.ProjectId,
+			},
+			Table: RAW_EXECUTION_TABLE,
+		},
+		Convert: func(inputRow interface{}) ([]interface{}, error) {
+			toolExecution := inputRow.(*models.ZentaoExecution)
+
+			domainBoard := &ticket.Board{
+				DomainEntity: domainlayer.DomainEntity{
+					Id: boardIdGen.Generate(toolExecution.ConnectionId, toolExecution.Id),
+				},
+				Name:        toolExecution.Name,
+				Description: toolExecution.Desc,
+				Url:         toolExecution.Path,
+				CreatedDate: toolExecution.OpenedDate.ToNullableTime(),
+				Type:        toolExecution.Type,
+			}
+
+			results := make([]interface{}, 0)
+			results = append(results, domainBoard)
+			return results, nil
+		},
+	})
+
+	if err != nil {
+		return err
+	}
+
+	return convertor.Execute()
+}


[incubator-devlake] 10/12: fix:apiurl&model error

Posted by wa...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

warren pushed a commit to branch feat-plugin-zentao
in repository https://gitbox.apache.org/repos/asf/incubator-devlake.git

commit 8f3ff2c706b7cdb4ca6691b159bde4faeee3545b
Author: yuqiangabab <11...@qq.com>
AuthorDate: Sun Oct 2 11:20:52 2022 +0800

    fix:apiurl&model error
---
 plugins/zentao/models/archived/stories.go | 116 +++++++-----------------------
 plugins/zentao/models/stories.go          | 116 +++++++-----------------------
 plugins/zentao/tasks/stories_collector.go |  12 +++-
 plugins/zentao/tasks/stories_convertor.go |  18 ++---
 plugins/zentao/tasks/stories_extractor.go |   5 +-
 plugins/zentao/tasks/task_data.go         |   1 -
 plugins/zentao/zentao.go                  |   2 -
 7 files changed, 75 insertions(+), 195 deletions(-)

diff --git a/plugins/zentao/models/archived/stories.go b/plugins/zentao/models/archived/stories.go
index cd608682..d197e1fd 100644
--- a/plugins/zentao/models/archived/stories.go
+++ b/plugins/zentao/models/archived/stories.go
@@ -25,11 +25,16 @@ import (
 type ZentaoStories struct {
 	archived.NoPKModel
 	ConnectionId uint64 `gorm:"primaryKey;type:BIGINT  NOT NULL"`
-	ID           int    `json:"id"gorm:"primaryKey;type:BIGINT  NOT NULL"`
-	Vision       string `json:"vision"`
-	Parent       int    `json:"parent"`
+	ExecutionId  uint64 `json:"execution_id"`
+	Project      int    `json:"project"`
 	Product      int    `json:"product"`
 	Branch       int    `json:"branch"`
+	Story        int    `json:"story"`
+	Version      int    `json:"version"`
+	Order        int    `json:"order"`
+	ID           int    `json:"id" gorm:"primaryKey;type:BIGINT  NOT NULL" `
+	Vision       string `json:"vision"`
+	Parent       int    `json:"parent"`
 	Module       int    `json:"module"`
 	Plan         string `json:"plan"`
 	Source       string `json:"source"`
@@ -47,20 +52,20 @@ type ZentaoStories struct {
 	Color        string `json:"color"`
 	Stage        string `json:"stage"`
 	StagedBy     string `json:"stagedBy"`
-	//Mailto           []interface{} `json:"mailto" gorm:"-:all"`
-	Lib              int `json:"lib"`
-	FromStory        int `json:"fromStory"`
-	FromVersion      int `json:"fromVersion"`
-	OpenedBy         `json:"openedBy"`
-	OpenedDate       *helper.Iso8601Time `json:"openedDate"`
-	AssignedTo       `json:"assignedTo"`
-	AssignedDate     *helper.Iso8601Time `json:"assignedDate"`
-	ApprovedDate     string              `json:"approvedDate"`
-	LastEditedBy     `json:"lastEditedBy"`
-	LastEditedDate   *helper.Iso8601Time `json:"lastEditedDate"`
-	ChangedBy        string              `json:"changedBy"`
-	ChangedDate      string              `json:"changedDate"`
-	ReviewedBy       interface{}         `json:"reviewedBy" gorm:"-:all"`
+	//Mailto           []interface{} `json:"mailto"`
+	Lib            int `json:"lib"`
+	FromStory      int `json:"fromStory"`
+	FromVersion    int `json:"fromVersion"`
+	OpenedBy       `json:"openedBy"`
+	OpenedDate     *helper.Iso8601Time `json:"openedDate"`
+	AssignedTo     `json:"assignedTo"`
+	AssignedDate   *helper.Iso8601Time `json:"assignedDate"`
+	ApprovedDate   string              `json:"approvedDate"`
+	LastEditedBy   `json:"lastEditedBy"`
+	LastEditedDate *helper.Iso8601Time `json:"lastEditedDate"`
+	ChangedBy      string              `json:"changedBy"`
+	ChangedDate    string              `json:"changedDate"`
+	//ReviewedBy       interface{} `json:"reviewedBy"`
 	ReviewedDate     *helper.Iso8601Time `json:"reviewedDate"`
 	ClosedBy         `json:"closedBy"`
 	ClosedDate       *helper.Iso8601Time `json:"closedDate"`
@@ -71,81 +76,17 @@ type ZentaoStories struct {
 	LinkStories      string              `json:"linkStories"`
 	LinkRequirements string              `json:"linkRequirements"`
 	DuplicateStory   int                 `json:"duplicateStory"`
-	Version          int                 `json:"version"`
 	StoryChanged     string              `json:"storyChanged"`
 	FeedbackBy       string              `json:"feedbackBy"`
 	NotifyEmail      string              `json:"notifyEmail"`
 	URChanged        string              `json:"URChanged"`
 	Deleted          bool                `json:"deleted"`
-	Spec             string              `json:"spec"`
-	Verify           string              `json:"verify"`
-	Executions       Executions          `json:"executions" gorm:"-:all"`
-	Tasks            []Tasks             `json:"tasks" gorm:"-:all"`
-	//Stages           []interface{}       `json:"stages" gorm:"-:all"`
-	PlanTitle []string `json:"planTitle" gorm:"-:all"`
-	//Children         []interface{}       `json:"children" gorm:"-:all"`
-	//Files            []interface{}       `json:"files" gorm:"-:all"`
-	ProductName   string  `json:"productName"`
-	ProductStatus string  `json:"productStatus"`
-	ModuleTitle   string  `json:"moduleTitle"`
-	Bugs          []Bugs  `json:"bugs" gorm:"-:all"`
-	Cases         []Cases `json:"cases" gorm:"-:all"`
-	//Requirements  []interface{} `json:"requirements" gorm:"-:all"`
-	Actions    []Actions `json:"actions" gorm:"-:all"`
-	PreAndNext `json:"preAndNext"`
-}
-type Executions struct {
-	Num1 struct {
-		Project int    `json:"project"`
-		Name    string `json:"name"`
-		Status  string `json:"status"`
-		Type    string `json:"type"`
-	} `json:"1"`
-}
-type Tasks struct {
-	ID         int    `json:"id"`
-	Name       string `json:"name"`
-	Type       string `json:"type"`
-	Status     string `json:"status"`
-	AssignedTo struct {
-		ID       int    `json:"id"`
-		Account  string `json:"account"`
-		Avatar   string `json:"avatar"`
-		Realname string `json:"realname"`
-	} `json:"assignedTo"`
-}
-type Bugs struct {
-	ID       int    `json:"id"`
-	Title    string `json:"title"`
-	Status   string `json:"status"`
-	Pri      int    `json:"pri"`
-	Severity int    `json:"severity"`
-}
-type Cases struct {
-	ID     int    `json:"id"`
-	Title  string `json:"title"`
-	Pri    int    `json:"pri"`
-	Status string `json:"status"`
+	PriOrder         string              `json:"priOrder"`
+	ProductType      string              `json:"productType"`
+	PlanTitle        string              `json:"planTitle"`
+	ProductStatus    string              `json:"productStatus"`
 }
 
-type Actions struct {
-	ID         int    `json:"id"`
-	ObjectType string `json:"objectType"`
-	ObjectID   int    `json:"objectID"`
-	Product    string `json:"product"`
-	Project    int    `json:"project"`
-	Execution  int    `json:"execution"`
-	Actor      string `json:"actor"`
-	Action     string `json:"action"`
-	Date       string `json:"date"`
-	Comment    string `json:"comment"`
-	Extra      string `json:"extra"`
-	Read       string `json:"read"`
-	Vision     string `json:"vision"`
-	Efforted   int    `json:"efforted"`
-	//History    []interface{} `json:"history"`
-	Desc string `json:"desc"`
-}
 type AssignedTo struct {
 	ID       int    `json:"id"`
 	Account  string `json:"account"`
@@ -153,11 +94,6 @@ type AssignedTo struct {
 	Realname string `json:"realname"`
 }
 
-type PreAndNext struct {
-	Pre  string `json:"pre"`
-	Next string `json:"next"`
-}
-
 func (ZentaoStories) TableName() string {
 	return "_tool_zentao_stories"
 }
diff --git a/plugins/zentao/models/stories.go b/plugins/zentao/models/stories.go
index be198bbf..1d65c111 100644
--- a/plugins/zentao/models/stories.go
+++ b/plugins/zentao/models/stories.go
@@ -25,11 +25,16 @@ import (
 type ZentaoStories struct {
 	common.NoPKModel
 	ConnectionId uint64 `gorm:"primaryKey;type:BIGINT  NOT NULL"`
-	ID           int    `json:"id"gorm:"primaryKey;type:BIGINT  NOT NULL"`
-	Vision       string `json:"vision"`
-	Parent       int    `json:"parent"`
+	ExecutionId  uint64 `json:"execution_id"`
+	Project      int    `json:"project"`
 	Product      int    `json:"product"`
 	Branch       int    `json:"branch"`
+	Story        int    `json:"story"`
+	Version      int    `json:"version"`
+	Order        int    `json:"order"`
+	ID           int    `json:"id" gorm:"primaryKey;type:BIGINT  NOT NULL" `
+	Vision       string `json:"vision"`
+	Parent       int    `json:"parent"`
 	Module       int    `json:"module"`
 	Plan         string `json:"plan"`
 	Source       string `json:"source"`
@@ -47,20 +52,20 @@ type ZentaoStories struct {
 	Color        string `json:"color"`
 	Stage        string `json:"stage"`
 	StagedBy     string `json:"stagedBy"`
-	//Mailto           []interface{} `json:"mailto" gorm:"-:all"`
-	Lib              int `json:"lib"`
-	FromStory        int `json:"fromStory"`
-	FromVersion      int `json:"fromVersion"`
-	OpenedBy         `json:"openedBy"`
-	OpenedDate       *helper.Iso8601Time `json:"openedDate"`
-	AssignedTo       `json:"assignedTo"`
-	AssignedDate     *helper.Iso8601Time `json:"assignedDate"`
-	ApprovedDate     string              `json:"approvedDate"`
-	LastEditedBy     `json:"lastEditedBy"`
-	LastEditedDate   *helper.Iso8601Time `json:"lastEditedDate"`
-	ChangedBy        string              `json:"changedBy"`
-	ChangedDate      string              `json:"changedDate"`
-	ReviewedBy       interface{}         `json:"reviewedBy" gorm:"-:all"`
+	//Mailto           []interface{} `json:"mailto"`
+	Lib            int `json:"lib"`
+	FromStory      int `json:"fromStory"`
+	FromVersion    int `json:"fromVersion"`
+	OpenedBy       `json:"openedBy"`
+	OpenedDate     *helper.Iso8601Time `json:"openedDate"`
+	AssignedTo     `json:"assignedTo"`
+	AssignedDate   *helper.Iso8601Time `json:"assignedDate"`
+	ApprovedDate   string              `json:"approvedDate"`
+	LastEditedBy   `json:"lastEditedBy"`
+	LastEditedDate *helper.Iso8601Time `json:"lastEditedDate"`
+	ChangedBy      string              `json:"changedBy"`
+	ChangedDate    string              `json:"changedDate"`
+	//ReviewedBy       interface{} `json:"reviewedBy"`
 	ReviewedDate     *helper.Iso8601Time `json:"reviewedDate"`
 	ClosedBy         `json:"closedBy"`
 	ClosedDate       *helper.Iso8601Time `json:"closedDate"`
@@ -71,81 +76,17 @@ type ZentaoStories struct {
 	LinkStories      string              `json:"linkStories"`
 	LinkRequirements string              `json:"linkRequirements"`
 	DuplicateStory   int                 `json:"duplicateStory"`
-	Version          int                 `json:"version"`
 	StoryChanged     string              `json:"storyChanged"`
 	FeedbackBy       string              `json:"feedbackBy"`
 	NotifyEmail      string              `json:"notifyEmail"`
 	URChanged        string              `json:"URChanged"`
 	Deleted          bool                `json:"deleted"`
-	Spec             string              `json:"spec"`
-	Verify           string              `json:"verify"`
-	Executions       Executions          `json:"executions" gorm:"-:all"`
-	Tasks            []Tasks             `json:"tasks" gorm:"-:all"`
-	//Stages           []interface{}       `json:"stages" gorm:"-:all"`
-	PlanTitle []string `json:"planTitle" gorm:"-:all"`
-	//Children         []interface{}       `json:"children" gorm:"-:all"`
-	//Files            []interface{}       `json:"files" gorm:"-:all"`
-	ProductName   string  `json:"productName"`
-	ProductStatus string  `json:"productStatus"`
-	ModuleTitle   string  `json:"moduleTitle"`
-	Bugs          []Bugs  `json:"bugs" gorm:"-:all"`
-	Cases         []Cases `json:"cases" gorm:"-:all"`
-	//Requirements  []interface{} `json:"requirements" gorm:"-:all"`
-	Actions    []Actions `json:"actions" gorm:"-:all"`
-	PreAndNext `json:"preAndNext"`
-}
-type Executions struct {
-	Num1 struct {
-		Project int    `json:"project"`
-		Name    string `json:"name"`
-		Status  string `json:"status"`
-		Type    string `json:"type"`
-	} `json:"1"`
-}
-type Tasks struct {
-	ID         int    `json:"id"`
-	Name       string `json:"name"`
-	Type       string `json:"type"`
-	Status     string `json:"status"`
-	AssignedTo struct {
-		ID       int    `json:"id"`
-		Account  string `json:"account"`
-		Avatar   string `json:"avatar"`
-		Realname string `json:"realname"`
-	} `json:"assignedTo"`
-}
-type Bugs struct {
-	ID       int    `json:"id"`
-	Title    string `json:"title"`
-	Status   string `json:"status"`
-	Pri      int    `json:"pri"`
-	Severity int    `json:"severity"`
-}
-type Cases struct {
-	ID     int    `json:"id"`
-	Title  string `json:"title"`
-	Pri    int    `json:"pri"`
-	Status string `json:"status"`
+	PriOrder         string              `json:"priOrder"`
+	ProductType      string              `json:"productType"`
+	PlanTitle        string              `json:"planTitle"`
+	ProductStatus    string              `json:"productStatus"`
 }
 
-type Actions struct {
-	ID         int    `json:"id"`
-	ObjectType string `json:"objectType"`
-	ObjectID   int    `json:"objectID"`
-	Product    string `json:"product"`
-	Project    int    `json:"project"`
-	Execution  int    `json:"execution"`
-	Actor      string `json:"actor"`
-	Action     string `json:"action"`
-	Date       string `json:"date"`
-	Comment    string `json:"comment"`
-	Extra      string `json:"extra"`
-	Read       string `json:"read"`
-	Vision     string `json:"vision"`
-	Efforted   int    `json:"efforted"`
-	//History    []interface{} `json:"history"`
-	Desc string `json:"desc"`
-}
 type AssignedTo struct {
 	ID       int    `json:"id"`
 	Account  string `json:"account"`
@@ -153,11 +94,6 @@ type AssignedTo struct {
 	Realname string `json:"realname"`
 }
 
-type PreAndNext struct {
-	Pre  string `json:"pre"`
-	Next string `json:"next"`
-}
-
 func (ZentaoStories) TableName() string {
 	return "_tool_zentao_stories"
 }
diff --git a/plugins/zentao/tasks/stories_collector.go b/plugins/zentao/tasks/stories_collector.go
index 6ba58924..26c004c8 100644
--- a/plugins/zentao/tasks/stories_collector.go
+++ b/plugins/zentao/tasks/stories_collector.go
@@ -37,7 +37,9 @@ func CollectStories(taskCtx core.SubTaskContext) error {
 		RawDataSubTaskArgs: helper.RawDataSubTaskArgs{
 			Ctx: taskCtx,
 			Params: ZentaoApiParams{
-				StoriesId: data.Options.StoriesId,
+				ProductId:   data.Options.ProductId,
+				ExecutionId: data.Options.ExecutionId,
+				ProjectId:   data.Options.ProjectId,
 			},
 			Table: RAW_STORIES_TABLE,
 		},
@@ -45,7 +47,7 @@ func CollectStories(taskCtx core.SubTaskContext) error {
 		Incremental: false,
 		PageSize:    100,
 		// TODO write which api would you want request
-		UrlTemplate: "/stories/{{ .Params.StoriesId }}",
+		UrlTemplate: "/executions/{{ .Params.ExecutionId }}/stories",
 		Query: func(reqData *helper.RequestData) (url.Values, error) {
 			query := url.Values{}
 			query.Set("page", fmt.Sprintf("%v", reqData.Pager.Page))
@@ -54,12 +56,16 @@ func CollectStories(taskCtx core.SubTaskContext) error {
 		},
 		GetTotalPages: GetTotalPagesFromResponse,
 		ResponseParser: func(res *http.Response) ([]json.RawMessage, error) {
+			var data struct {
+				Stories []json.RawMessage `json:"stories"`
+			}
 			body, err := io.ReadAll(res.Body)
+			json.Unmarshal(body, &data)
 			res.Body.Close()
 			if err != nil {
 				return nil, err
 			}
-			return []json.RawMessage{body}, nil
+			return data.Stories, nil
 		},
 	})
 	if err != nil {
diff --git a/plugins/zentao/tasks/stories_convertor.go b/plugins/zentao/tasks/stories_convertor.go
index a41a1868..b9d80724 100644
--- a/plugins/zentao/tasks/stories_convertor.go
+++ b/plugins/zentao/tasks/stories_convertor.go
@@ -44,8 +44,8 @@ func ConvertStories(taskCtx core.SubTaskContext) error {
 	boardIdGen := didgen.NewDomainIdGenerator(&models.ZentaoStories{})
 	cursor, err := db.Cursor(
 		dal.From(&models.ZentaoStories{}),
-		dal.Where(`_tool_zentao_stories.id = ? and 
-			_tool_zentao_stories.connection_id = ?`, data.Options.StoriesId, data.Options.ConnectionId),
+		dal.Where(`_tool_zentao_stories.execution_id = ? and 
+			_tool_zentao_stories.connection_id = ?`, data.Options.ExecutionId, data.Options.ConnectionId),
 	)
 	if err != nil {
 		return err
@@ -57,7 +57,9 @@ func ConvertStories(taskCtx core.SubTaskContext) error {
 		RawDataSubTaskArgs: helper.RawDataSubTaskArgs{
 			Ctx: taskCtx,
 			Params: ZentaoApiParams{
-				StoriesId: data.Options.StoriesId,
+				ProductId:   data.Options.ProductId,
+				ExecutionId: data.Options.ExecutionId,
+				ProjectId:   data.Options.ProjectId,
 			},
 			Table: RAW_STORIES_TABLE,
 		},
@@ -68,11 +70,11 @@ func ConvertStories(taskCtx core.SubTaskContext) error {
 				DomainEntity: domainlayer.DomainEntity{
 					Id: boardIdGen.Generate(toolStories.ConnectionId, toolStories.ID),
 				},
-				Url:                     "",
-				IconURL:                 "",
-				IssueKey:                "",
-				Title:                   toolStories.Title,
-				Description:             toolStories.Spec,
+				Url:      "",
+				IconURL:  "",
+				IssueKey: "",
+				Title:    toolStories.Title,
+				//Description:             toolStories.Spec,
 				EpicKey:                 "",
 				Type:                    toolStories.Type,
 				Status:                  toolStories.Status,
diff --git a/plugins/zentao/tasks/stories_extractor.go b/plugins/zentao/tasks/stories_extractor.go
index aaadfb84..a4d37db0 100644
--- a/plugins/zentao/tasks/stories_extractor.go
+++ b/plugins/zentao/tasks/stories_extractor.go
@@ -40,7 +40,9 @@ func ExtractStories(taskCtx core.SubTaskContext) error {
 		RawDataSubTaskArgs: helper.RawDataSubTaskArgs{
 			Ctx: taskCtx,
 			Params: ZentaoApiParams{
-				StoriesId: data.Options.StoriesId,
+				ProductId:   data.Options.ProductId,
+				ExecutionId: data.Options.ExecutionId,
+				ProjectId:   data.Options.ProjectId,
 			},
 			Table: RAW_STORIES_TABLE,
 		},
@@ -51,6 +53,7 @@ func ExtractStories(taskCtx core.SubTaskContext) error {
 				return nil, err
 			}
 			stories.ConnectionId = data.Options.ConnectionId
+			stories.ExecutionId = data.Options.ExecutionId
 			results := make([]interface{}, 0)
 			results = append(results, stories)
 			return results, nil
diff --git a/plugins/zentao/tasks/task_data.go b/plugins/zentao/tasks/task_data.go
index ae51908f..c522360e 100644
--- a/plugins/zentao/tasks/task_data.go
+++ b/plugins/zentao/tasks/task_data.go
@@ -27,7 +27,6 @@ type ZentaoApiParams struct {
 	ProductId   uint64
 	ExecutionId uint64
 	ProjectId   uint64
-	StoriesId   uint64
 }
 
 type ZentaoOptions struct {
diff --git a/plugins/zentao/zentao.go b/plugins/zentao/zentao.go
index 3768b5d9..32845c90 100644
--- a/plugins/zentao/zentao.go
+++ b/plugins/zentao/zentao.go
@@ -34,7 +34,6 @@ func main() {
 	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")
-	storiesId := cmd.Flags().IntP("storiesId", "s", 1, "stories id")
 
 	cmd.Run = func(cmd *cobra.Command, args []string) {
 		runner.DirectRun(cmd, args, PluginEntry, map[string]interface{}{
@@ -42,7 +41,6 @@ func main() {
 			"executionId":  *executionId,
 			"productId":    *productId,
 			"projectId":    *projectId,
-			"storiesId":    *storiesId,
 		})
 	}
 	runner.RunCmd(cmd)


[incubator-devlake] 05/12: feat:add zentao project extractor

Posted by wa...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

warren pushed a commit to branch feat-plugin-zentao
in repository https://gitbox.apache.org/repos/asf/incubator-devlake.git

commit 34298a21aad879dac19bc884f53f6742ccdfd6ee
Author: yuqiangabab <11...@qq.com>
AuthorDate: Sun Sep 18 18:54:15 2022 +0800

    feat:add zentao project extractor
---
 plugins/zentao/impl/impl.go                        |  83 ++++++++-------
 plugins/zentao/models/archived/project.go          | 113 +++++++++++++++++++++
 .../migrationscripts/20220906_add_init_tables.go   |   1 +
 plugins/zentao/tasks/project_extractor.go          |  63 ++++++++++++
 4 files changed, 218 insertions(+), 42 deletions(-)

diff --git a/plugins/zentao/impl/impl.go b/plugins/zentao/impl/impl.go
index cc81c226..f24f6afe 100644
--- a/plugins/zentao/impl/impl.go
+++ b/plugins/zentao/impl/impl.go
@@ -21,11 +21,11 @@ import (
 	"fmt"
 	"github.com/apache/incubator-devlake/migration"
 	"github.com/apache/incubator-devlake/plugins/core"
-    "github.com/apache/incubator-devlake/plugins/zentao/api"
-    "github.com/apache/incubator-devlake/plugins/zentao/models"
-    "github.com/apache/incubator-devlake/plugins/zentao/models/migrationscripts"
-	"github.com/apache/incubator-devlake/plugins/zentao/tasks"
 	"github.com/apache/incubator-devlake/plugins/helper"
+	"github.com/apache/incubator-devlake/plugins/zentao/api"
+	"github.com/apache/incubator-devlake/plugins/zentao/models"
+	"github.com/apache/incubator-devlake/plugins/zentao/models/migrationscripts"
+	"github.com/apache/incubator-devlake/plugins/zentao/tasks"
 	"github.com/spf13/viper"
 	"gorm.io/gorm"
 )
@@ -38,8 +38,6 @@ var _ core.PluginApi = (*Zentao)(nil)
 var _ core.PluginBlueprintV100 = (*Zentao)(nil)
 var _ core.CloseablePluginTask = (*Zentao)(nil)
 
-
-
 type Zentao struct{}
 
 func (plugin Zentao) Description() string {
@@ -55,33 +53,34 @@ func (plugin Zentao) SubTaskMetas() []core.SubTaskMeta {
 	// TODO add your sub task here
 	return []core.SubTaskMeta{
 		tasks.CollectProjectMeta,
+		tasks.ExtractProjectsMeta,
 	}
 }
 
 func (plugin Zentao) PrepareTaskData(taskCtx core.TaskContext, options map[string]interface{}) (interface{}, error) {
 	op, err := tasks.DecodeAndValidateTaskOptions(options)
-    if err != nil {
-        return nil, err
-    }
-    connectionHelper := helper.NewConnectionHelper(
-        taskCtx,
-        nil,
-    )
-    connection := &models.ZentaoConnection{}
-    err = connectionHelper.FirstById(connection, op.ConnectionId)
-    if err != nil {
-        return nil, fmt.Errorf("unable to get Zentao connection by the given connection ID: %v", err)
-    }
-
-    apiClient, err := tasks.NewZentaoApiClient(taskCtx, connection)
-    if err != nil {
-        return nil, fmt.Errorf("unable to get Zentao API client instance: %v", err)
-    }
-
-    return &tasks.ZentaoTaskData{
-        Options:   op,
-        ApiClient: apiClient,
-    }, nil
+	if err != nil {
+		return nil, err
+	}
+	connectionHelper := helper.NewConnectionHelper(
+		taskCtx,
+		nil,
+	)
+	connection := &models.ZentaoConnection{}
+	err = connectionHelper.FirstById(connection, op.ConnectionId)
+	if err != nil {
+		return nil, fmt.Errorf("unable to get Zentao connection by the given connection ID: %v", err)
+	}
+
+	apiClient, err := tasks.NewZentaoApiClient(taskCtx, connection)
+	if err != nil {
+		return nil, fmt.Errorf("unable to get Zentao API client instance: %v", err)
+	}
+
+	return &tasks.ZentaoTaskData{
+		Options:   op,
+		ApiClient: apiClient,
+	}, nil
 }
 
 // PkgPath information lost when compiled as plugin(.so)
@@ -94,20 +93,20 @@ func (plugin Zentao) MigrationScripts() []migration.Script {
 }
 
 func (plugin Zentao) ApiResources() map[string]map[string]core.ApiResourceHandler {
-    return map[string]map[string]core.ApiResourceHandler{
-        "test": {
-            "POST": api.TestConnection,
-        },
-        "connections": {
-            "POST": api.PostConnections,
-            "GET":  api.ListConnections,
-        },
-        "connections/:connectionId": {
-            "GET":    api.GetConnection,
-            "PATCH":  api.PatchConnection,
-            "DELETE": api.DeleteConnection,
-        },
-    }
+	return map[string]map[string]core.ApiResourceHandler{
+		"test": {
+			"POST": api.TestConnection,
+		},
+		"connections": {
+			"POST": api.PostConnections,
+			"GET":  api.ListConnections,
+		},
+		"connections/:connectionId": {
+			"GET":    api.GetConnection,
+			"PATCH":  api.PatchConnection,
+			"DELETE": api.DeleteConnection,
+		},
+	}
 }
 
 func (plugin Zentao) MakePipelinePlan(connectionId uint64, scope []*core.BlueprintScopeV100) (core.PipelinePlan, error) {
diff --git a/plugins/zentao/models/archived/project.go b/plugins/zentao/models/archived/project.go
new file mode 100644
index 00000000..db79cceb
--- /dev/null
+++ b/plugins/zentao/models/archived/project.go
@@ -0,0 +1,113 @@
+/*
+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 archived
+
+import (
+	"github.com/apache/incubator-devlake/models/common"
+	"time"
+)
+
+type ZentaoProject struct {
+	common.NoPKModel
+	ConnectionId  uint64 `gorm:"primaryKey;type:BIGINT  NOT NULL"`
+	ID            int    `json:"id"`
+	Project       int    `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        int    `json:"parent"`
+	Path          string `json:"path"`
+	Grade         int    `json:"grade"`
+	Name          string `json:"name"`
+	Code          string `json:"code"`
+	Begin         string `json:"begin"`
+	End           string `json:"end"`
+	RealBegan     string `json:"realBegan"`
+	RealEnd       string `json:"realEnd"`
+	Days          int    `json:"days"`
+	Status        string `json:"status"`
+	SubStatus     string `json:"subStatus"`
+	Pri           string `json:"pri"`
+	Desc          string `json:"desc"`
+	Version       int    `json:"version"`
+	ParentVersion int    `json:"parentVersion"`
+	PlanDuration  int    `json:"planDuration"`
+	RealDuration  int    `json:"realDuration"`
+	//OpenedBy       string    `json:"openedBy"`
+	OpenedDate     time.Time `json:"openedDate"`
+	OpenedVersion  string    `json:"openedVersion"`
+	LastEditedBy   string    `json:"lastEditedBy"`
+	LastEditedDate time.Time `json:"lastEditedDate"`
+	//ClosedBy       string    `json:"closedBy"`
+	//ClosedDate     string    `json:"closedDate"`
+	//CanceledBy string `json:"canceledBy"`
+	//CanceledDate   string    `json:"canceledDate"`
+	SuspendedDate string `json:"suspendedDate"`
+	PO            string `json:"PO"`
+	PM            `json:"PM"`
+	QD            string `json:"QD"`
+	RD            string `json:"RD"`
+	Team          string `json:"team"`
+	Acl           string `json:"acl"`
+	Whitelist     `json:"whitelist" gorm:"-"`
+	Order         int    `json:"order"`
+	Vision        string `json:"vision"`
+	DisplayCards  int    `json:"displayCards"`
+	FluidBoard    string `json:"fluidBoard"`
+	Deleted       bool   `json:"deleted"`
+	Delay         int    `json:"delay"`
+	Hours         `json:"hours"`
+	TeamCount     int    `json:"teamCount"`
+	LeftTasks     string `json:"leftTasks"`
+	//TeamMembers   []interface{} `json:"teamMembers" gorm:"-"`
+	TotalEstimate int `json:"totalEstimate"`
+	TotalConsumed int `json:"totalConsumed"`
+	TotalLeft     int `json:"totalLeft"`
+	Progress      int `json:"progress"`
+	TotalReal     int `json:"totalReal"`
+}
+type PM struct {
+	PmId       int    `json:"id"`
+	PmAccount  string `json:"account"`
+	PmAvatar   string `json:"avatar"`
+	PmRealname string `json:"realname"`
+}
+type Whitelist []struct {
+	WhitelistID       int    `json:"id"`
+	WhitelistAccount  string `json:"account"`
+	WhitelistAvatar   string `json:"avatar"`
+	WhitelistRealname string `json:"realname"`
+}
+type Hours struct {
+	HoursTotalEstimate int `json:"totalEstimate"`
+	HoursTotalConsumed int `json:"totalConsumed"`
+	HoursTotalLeft     int `json:"totalLeft"`
+	HoursProgress      int `json:"progress"`
+	HoursTotalReal     int `json:"totalReal"`
+}
+
+func (ZentaoProject) TableName() string {
+	return "_tool_zentao_project"
+}
diff --git a/plugins/zentao/models/migrationscripts/20220906_add_init_tables.go b/plugins/zentao/models/migrationscripts/20220906_add_init_tables.go
index fa9f9684..1a743c46 100644
--- a/plugins/zentao/models/migrationscripts/20220906_add_init_tables.go
+++ b/plugins/zentao/models/migrationscripts/20220906_add_init_tables.go
@@ -28,6 +28,7 @@ type addInitTables struct{}
 func (u *addInitTables) Up(ctx context.Context, db *gorm.DB) error {
 	return db.Migrator().AutoMigrate(
 		archived.ZentaoConnection{},
+		archived.ZentaoProject{},
 	)
 }
 
diff --git a/plugins/zentao/tasks/project_extractor.go b/plugins/zentao/tasks/project_extractor.go
new file mode 100644
index 00000000..6cc9d9e5
--- /dev/null
+++ b/plugins/zentao/tasks/project_extractor.go
@@ -0,0 +1,63 @@
+/*
+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/plugins/core"
+	"github.com/apache/incubator-devlake/plugins/helper"
+	"github.com/apache/incubator-devlake/plugins/zentao/models/archived"
+)
+
+var _ core.SubTaskEntryPoint = ExtractProjects
+
+var ExtractProjectsMeta = core.SubTaskMeta{
+	Name:             "extractProjects",
+	EntryPoint:       ExtractProjects,
+	EnabledByDefault: true,
+	Description:      "extract Zentao projects",
+	DomainTypes:      []string{core.DOMAIN_TYPE_TICKET},
+}
+
+func ExtractProjects(taskCtx core.SubTaskContext) error {
+	data := taskCtx.GetData().(*ZentaoTaskData)
+	extractor, err := helper.NewApiExtractor(helper.ApiExtractorArgs{
+		RawDataSubTaskArgs: helper.RawDataSubTaskArgs{
+			Ctx:    taskCtx,
+			Params: ZentaoApiParams{},
+			Table:  RAW_PROJECT_TABLE,
+		},
+		Extract: func(row *helper.RawData) ([]interface{}, error) {
+			project := &archived.ZentaoProject{}
+			err := json.Unmarshal(row.Data, project)
+			if err != nil {
+				return nil, err
+			}
+			project.ConnectionId = data.Options.ConnectionId
+			results := make([]interface{}, 0)
+			results = append(results, project)
+			return results, nil
+		},
+	})
+
+	if err != nil {
+		return err
+	}
+
+	return extractor.Execute()
+}


[incubator-devlake] 06/12: fix(zentao): fix minor issues

Posted by wa...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

warren pushed a commit to branch feat-plugin-zentao
in repository https://gitbox.apache.org/repos/asf/incubator-devlake.git

commit f0bd0762c859b6718fae97d1f3cb8945cffa6959
Author: Yingchu Chen <yi...@merico.dev>
AuthorDate: Sun Sep 18 21:40:57 2022 +0800

    fix(zentao): fix minor issues
---
 plugins/zentao/models/archived/project.go       | 54 ++++++++++++-------------
 plugins/zentao/models/{archived => }/project.go | 52 ++++++++++++------------
 plugins/zentao/tasks/project_extractor.go       |  4 +-
 3 files changed, 55 insertions(+), 55 deletions(-)

diff --git a/plugins/zentao/models/archived/project.go b/plugins/zentao/models/archived/project.go
index db79cceb..63c498dd 100644
--- a/plugins/zentao/models/archived/project.go
+++ b/plugins/zentao/models/archived/project.go
@@ -18,12 +18,12 @@ limitations under the License.
 package archived
 
 import (
-	"github.com/apache/incubator-devlake/models/common"
+	"github.com/apache/incubator-devlake/models/migrationscripts/archived"
 	"time"
 )
 
 type ZentaoProject struct {
-	common.NoPKModel
+	archived.NoPKModel
 	ConnectionId  uint64 `gorm:"primaryKey;type:BIGINT  NOT NULL"`
 	ID            int    `json:"id"`
 	Project       int    `json:"project"`
@@ -56,31 +56,31 @@ type ZentaoProject struct {
 	PlanDuration  int    `json:"planDuration"`
 	RealDuration  int    `json:"realDuration"`
 	//OpenedBy       string    `json:"openedBy"`
-	OpenedDate     time.Time `json:"openedDate"`
-	OpenedVersion  string    `json:"openedVersion"`
-	LastEditedBy   string    `json:"lastEditedBy"`
-	LastEditedDate time.Time `json:"lastEditedDate"`
-	//ClosedBy       string    `json:"closedBy"`
-	//ClosedDate     string    `json:"closedDate"`
-	//CanceledBy string `json:"canceledBy"`
-	//CanceledDate   string    `json:"canceledDate"`
-	SuspendedDate string `json:"suspendedDate"`
-	PO            string `json:"PO"`
-	PM            `json:"PM"`
-	QD            string `json:"QD"`
-	RD            string `json:"RD"`
-	Team          string `json:"team"`
-	Acl           string `json:"acl"`
-	Whitelist     `json:"whitelist" gorm:"-"`
-	Order         int    `json:"order"`
-	Vision        string `json:"vision"`
-	DisplayCards  int    `json:"displayCards"`
-	FluidBoard    string `json:"fluidBoard"`
-	Deleted       bool   `json:"deleted"`
-	Delay         int    `json:"delay"`
-	Hours         `json:"hours"`
-	TeamCount     int    `json:"teamCount"`
-	LeftTasks     string `json:"leftTasks"`
+	OpenedDate     time.Time  `json:"openedDate"`
+	OpenedVersion  string     `json:"openedVersion"`
+	LastEditedBy   string     `json:"lastEditedBy"`
+	LastEditedDate *time.Time `json:"lastEditedDate"`
+	ClosedBy       string     `json:"closedBy"`
+	ClosedDate     *time.Time `json:"closedDate"`
+	CanceledBy     string     `json:"canceledBy"`
+	CanceledDate   *time.Time `json:"canceledDate"`
+	SuspendedDate  string     `json:"suspendedDate"`
+	PO             string     `json:"PO"`
+	PM             `json:"PM"`
+	QD             string `json:"QD"`
+	RD             string `json:"RD"`
+	Team           string `json:"team"`
+	Acl            string `json:"acl"`
+	Whitelist      `json:"whitelist" gorm:"-"`
+	Order          int    `json:"order"`
+	Vision         string `json:"vision"`
+	DisplayCards   int    `json:"displayCards"`
+	FluidBoard     string `json:"fluidBoard"`
+	Deleted        bool   `json:"deleted"`
+	Delay          int    `json:"delay"`
+	Hours          `json:"hours"`
+	TeamCount      int    `json:"teamCount"`
+	LeftTasks      string `json:"leftTasks"`
 	//TeamMembers   []interface{} `json:"teamMembers" gorm:"-"`
 	TotalEstimate int `json:"totalEstimate"`
 	TotalConsumed int `json:"totalConsumed"`
diff --git a/plugins/zentao/models/archived/project.go b/plugins/zentao/models/project.go
similarity index 73%
copy from plugins/zentao/models/archived/project.go
copy to plugins/zentao/models/project.go
index db79cceb..5a51d738 100644
--- a/plugins/zentao/models/archived/project.go
+++ b/plugins/zentao/models/project.go
@@ -15,7 +15,7 @@ See the License for the specific language governing permissions and
 limitations under the License.
 */
 
-package archived
+package models
 
 import (
 	"github.com/apache/incubator-devlake/models/common"
@@ -56,31 +56,31 @@ type ZentaoProject struct {
 	PlanDuration  int    `json:"planDuration"`
 	RealDuration  int    `json:"realDuration"`
 	//OpenedBy       string    `json:"openedBy"`
-	OpenedDate     time.Time `json:"openedDate"`
-	OpenedVersion  string    `json:"openedVersion"`
-	LastEditedBy   string    `json:"lastEditedBy"`
-	LastEditedDate time.Time `json:"lastEditedDate"`
-	//ClosedBy       string    `json:"closedBy"`
-	//ClosedDate     string    `json:"closedDate"`
-	//CanceledBy string `json:"canceledBy"`
-	//CanceledDate   string    `json:"canceledDate"`
-	SuspendedDate string `json:"suspendedDate"`
-	PO            string `json:"PO"`
-	PM            `json:"PM"`
-	QD            string `json:"QD"`
-	RD            string `json:"RD"`
-	Team          string `json:"team"`
-	Acl           string `json:"acl"`
-	Whitelist     `json:"whitelist" gorm:"-"`
-	Order         int    `json:"order"`
-	Vision        string `json:"vision"`
-	DisplayCards  int    `json:"displayCards"`
-	FluidBoard    string `json:"fluidBoard"`
-	Deleted       bool   `json:"deleted"`
-	Delay         int    `json:"delay"`
-	Hours         `json:"hours"`
-	TeamCount     int    `json:"teamCount"`
-	LeftTasks     string `json:"leftTasks"`
+	OpenedDate     time.Time  `json:"openedDate"`
+	OpenedVersion  string     `json:"openedVersion"`
+	LastEditedBy   string     `json:"lastEditedBy"`
+	LastEditedDate *time.Time `json:"lastEditedDate,string"`
+	ClosedBy       string     `json:"closedBy"`
+	ClosedDate     *time.Time `json:"closedDate,string"`
+	CanceledBy     string     `json:"canceledBy"`
+	CanceledDate   *time.Time `json:"canceledDate,string"`
+	SuspendedDate  string     `json:"suspendedDate"`
+	PO             string     `json:"PO"`
+	PM             `json:"PM"`
+	QD             string `json:"QD"`
+	RD             string `json:"RD"`
+	Team           string `json:"team"`
+	Acl            string `json:"acl"`
+	Whitelist      `json:"whitelist" gorm:"-"`
+	Order          int    `json:"order"`
+	Vision         string `json:"vision"`
+	DisplayCards   int    `json:"displayCards"`
+	FluidBoard     string `json:"fluidBoard"`
+	Deleted        bool   `json:"deleted"`
+	Delay          int    `json:"delay"`
+	Hours          `json:"hours"`
+	TeamCount      int    `json:"teamCount"`
+	LeftTasks      string `json:"leftTasks"`
 	//TeamMembers   []interface{} `json:"teamMembers" gorm:"-"`
 	TotalEstimate int `json:"totalEstimate"`
 	TotalConsumed int `json:"totalConsumed"`
diff --git a/plugins/zentao/tasks/project_extractor.go b/plugins/zentao/tasks/project_extractor.go
index 6cc9d9e5..a1388efa 100644
--- a/plugins/zentao/tasks/project_extractor.go
+++ b/plugins/zentao/tasks/project_extractor.go
@@ -21,7 +21,7 @@ import (
 	"encoding/json"
 	"github.com/apache/incubator-devlake/plugins/core"
 	"github.com/apache/incubator-devlake/plugins/helper"
-	"github.com/apache/incubator-devlake/plugins/zentao/models/archived"
+	"github.com/apache/incubator-devlake/plugins/zentao/models"
 )
 
 var _ core.SubTaskEntryPoint = ExtractProjects
@@ -43,7 +43,7 @@ func ExtractProjects(taskCtx core.SubTaskContext) error {
 			Table:  RAW_PROJECT_TABLE,
 		},
 		Extract: func(row *helper.RawData) ([]interface{}, error) {
-			project := &archived.ZentaoProject{}
+			project := &models.ZentaoProject{}
 			err := json.Unmarshal(row.Data, project)
 			if err != nil {
 				return nil, err


[incubator-devlake] 02/12: feat(zentao): create new plugin

Posted by wa...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

warren pushed a commit to branch feat-plugin-zentao
in repository https://gitbox.apache.org/repos/asf/incubator-devlake.git

commit 74f74a8485479f794fbd89eda52c9059767cae94
Author: Yingchu Chen <yi...@merico.dev>
AuthorDate: Tue Sep 6 18:03:04 2022 +0800

    feat(zentao): create new plugin
    
    Relate to #2961
---
 plugins/zentao/api/connection.go      | 11 +++++------
 plugins/zentao/models/access_token.go | 27 +++++++++++++++++++++++++++
 plugins/zentao/tasks/api_client.go    | 13 ++++++-------
 3 files changed, 38 insertions(+), 13 deletions(-)

diff --git a/plugins/zentao/api/connection.go b/plugins/zentao/api/connection.go
index 2f8183d5..fe702185 100644
--- a/plugins/zentao/api/connection.go
+++ b/plugins/zentao/api/connection.go
@@ -21,7 +21,6 @@ import (
 	"context"
 	"github.com/apache/incubator-devlake/errors"
 	"github.com/apache/incubator-devlake/plugins/core"
-	"github.com/apache/incubator-devlake/plugins/feishu/apimodels"
 	"github.com/apache/incubator-devlake/plugins/helper"
 	"github.com/apache/incubator-devlake/plugins/zentao/models"
 	"github.com/mitchellh/mapstructure"
@@ -47,20 +46,20 @@ func TestConnection(input *core.ApiResourceInput) (*core.ApiResourceOutput, erro
 	}
 
 	// request for access token
-	tokenReqBody := &apimodels.ApiAccessTokenRequest{
-		AppId:     params.Username,
-		AppSecret: params.Password,
+	tokenReqBody := &models.ApiAccessTokenRequest{
+		Account:  params.Username,
+		Password: params.Password,
 	}
 	tokenRes, err := authApiClient.Post("/tokens", nil, tokenReqBody, nil)
 	if err != nil {
 		return nil, err
 	}
-	tokenResBody := &apimodels.ApiAccessTokenResponse{}
+	tokenResBody := &models.ApiAccessTokenResponse{}
 	err = helper.UnmarshalResponse(tokenRes, tokenResBody)
 	if err != nil {
 		return nil, err
 	}
-	if tokenResBody.AppAccessToken == "" && tokenResBody.TenantAccessToken == "" {
+	if tokenResBody.Token == "" {
 		return nil, errors.Default.New("failed to request access token")
 	}
 
diff --git a/plugins/zentao/models/access_token.go b/plugins/zentao/models/access_token.go
new file mode 100644
index 00000000..60bf2617
--- /dev/null
+++ b/plugins/zentao/models/access_token.go
@@ -0,0 +1,27 @@
+/*
+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 models
+
+type ApiAccessTokenRequest struct {
+	Account  string `json:"account"`
+	Password string `json:"password"`
+}
+
+type ApiAccessTokenResponse struct {
+	Token string `json:"token"`
+}
diff --git a/plugins/zentao/tasks/api_client.go b/plugins/zentao/tasks/api_client.go
index 62d2d954..17d490fb 100644
--- a/plugins/zentao/tasks/api_client.go
+++ b/plugins/zentao/tasks/api_client.go
@@ -20,7 +20,6 @@ package tasks
 import (
 	"fmt"
 	"github.com/apache/incubator-devlake/errors"
-	"github.com/apache/incubator-devlake/plugins/feishu/apimodels"
 	"net/http"
 	"strconv"
 	"time"
@@ -37,20 +36,20 @@ func NewZentaoApiClient(taskCtx core.TaskContext, connection *models.ZentaoConne
 	}
 
 	// request for access token
-	tokenReqBody := &apimodels.ApiAccessTokenRequest{
-		AppId:     connection.Username,
-		AppSecret: connection.Password,
+	tokenReqBody := &models.ApiAccessTokenRequest{
+		Account:  connection.Username,
+		Password: connection.Password,
 	}
 	tokenRes, err := authApiClient.Post("/tokens", nil, tokenReqBody, nil)
 	if err != nil {
 		return nil, err
 	}
-	tokenResBody := &apimodels.ApiAccessTokenResponse{}
+	tokenResBody := &models.ApiAccessTokenResponse{}
 	err = helper.UnmarshalResponse(tokenRes, tokenResBody)
 	if err != nil {
 		return nil, err
 	}
-	if tokenResBody.AppAccessToken == "" && tokenResBody.TenantAccessToken == "" {
+	if tokenResBody.Token == "" {
 		return nil, errors.Default.New("failed to request access token")
 	}
 	// real request apiClient
@@ -60,7 +59,7 @@ func NewZentaoApiClient(taskCtx core.TaskContext, connection *models.ZentaoConne
 	}
 	// set token
 	apiClient.SetHeaders(map[string]string{
-		"Token": fmt.Sprintf("%v", tokenResBody.TenantAccessToken),
+		"Token": fmt.Sprintf("%v", tokenResBody.Token),
 	})
 
 	// create rate limit calculator