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 <[email protected]>
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)

Reply via email to