diff --git a/README.md b/README.md index 9efb945..89ab952 100644 --- a/README.md +++ b/README.md @@ -53,13 +53,20 @@ Zentao API client enabling Go programs to interact with Zentao in a simple and u - [x] 获取产品发布列表 - [x] 需求(Stories) - [x] 获取项目需求列表 - - [x] 变更需求 - [x] 获取产品需求列表 - - [x] 创建需求 - [x] 获取执行需求列表 + - [x] 变更需求 + - [x] 创建需求 - [x] 获取需求详情 - [x] 删除需求 - [x] 修改需求其他字段 + - [x] 激活需求 + - [x] 关闭需求 + - [x] 指派需求 + - [ ] 预估工时 + - [ ] 子需求 + - [ ] 撤回评审 + - [ ] 评审需求 - [x] 项目(Projects) - [x] 创建项目 - [x] 获取项目列表 diff --git a/example/stories/main.go b/example/stories/main.go index 6ce9f53..0f933e6 100644 --- a/example/stories/main.go +++ b/example/stories/main.go @@ -20,6 +20,8 @@ import ( "log" "github.com/easysoft/go-zentao/v20/zentao" + + "github.com/davecgh/go-spew/spew" ) func main() { @@ -32,16 +34,74 @@ func main() { zentao.WithoutProxy(), ) if err != nil { - log.Fatal(err) + panic(err) + } + p1, _, err := zt.Stories.ProductsList(1) + if err != nil { + panic(err) + } + log.Printf("product 1 stories count: %v", len(p1.Stories)) + p2, _, err := zt.Stories.ProjectsList(2) + if err != nil { + panic(err) + } + log.Printf("product 1 stories count: %v", len(p2.Stories)) + createMsg := zentao.StoriesCreateMeta{ + Product: 1, + } + createMsg.Title = "test story" + createMsg.Pri = 1 + createMsg.Category = zentao.CategoryFeature + createMsg.Spec = "test spec" + // 可选 + createMsg.Verify = "test verify" + p3, _, err := zt.Stories.Create(createMsg) + if err != nil { + panic(err) + } + log.Printf("created story id: %v", p3.ID) + p4, _, err := zt.Stories.UpdateByID(p3.ID, zentao.StoriesMeta{ + Title: "test story updated", + }) + if err != nil { + panic(err) + } + log.Printf("updated story id: %v", p4.ID) + uptateMsg := zentao.StoriesUpdateFieldMeta{} + uptateMsg.Category = zentao.CategoryOther + uptateMsg.Reviewer = []string{"dev1"} + p5, _, err := zt.Stories.UpdateFieldByID(p4.ID, uptateMsg) + if err != nil { + panic(err) + } + log.Printf("updated story filed id: %v", p5.ID) + p6, _, err := zt.Stories.GetByID(p4.ID) + if err != nil { + panic(err) + } + spew.Dump(p6) + p7, _, err := zt.Stories.CloseByID(p6.ID, zentao.StoriesClose{ + Closedreason: zentao.CloseReasonBydesign, + Comment: "test close", + }) + if err != nil { + panic(err) } - pds, _, err := zt.Products.List() + log.Printf("close story id: %v", p7.ID) + p8, _, err := zt.Stories.ActiveByID(p7.ID, zentao.StoriesActive{ + Comment: "test active", + AssignedTo: "dev1", + }) if err != nil { - log.Fatal(err) + panic(err) } - log.Printf("Products count: %v", len(pds.Products)) - pgs, _, err := zt.Programs.List("") + log.Printf("active story id: %v", p8.ID) + p9, _, err := zt.Stories.AssignByID(p8.ID, zentao.StoriesActive{ + Comment: "test assign", + AssignedTo: "dev2", + }) if err != nil { - log.Fatal(err) + panic(err) } - log.Printf("Programs count: %v", len(pgs.Programs)) + log.Printf("assign story id: %v", p9.ID) } diff --git a/zentao/stories.go b/zentao/stories.go index bdef559..4002f33 100644 --- a/zentao/stories.go +++ b/zentao/stories.go @@ -40,50 +40,55 @@ type StoriesMeta struct { } type StoriesExtMeta struct { - Source string `json:"source,omitempty"` - SourceNote string `json:"sourceNote,omitempty"` - Pri int `json:"pri,omitempty"` - Category string `json:"category,omitempty"` - Estimate float64 `json:"estimate,omitempty"` - Keywords string `json:"keywords,omitempty"` + Source StoriesSource `json:"source,omitempty"` + SourceNote string `json:"sourceNote,omitempty"` // 来源备注 + Pri int `json:"pri,omitempty"` + Category StoriesCategory `json:"category,omitempty"` + Estimate float64 `json:"estimate,omitempty"` // 预估工时 + Keywords string `json:"keywords,omitempty"` + Parent any `json:"parent,omitempty"` // 可能是数组, 产品计划时是数组 + Reviewer []string `json:"reviewer,omitempty"` } type StoriesBody struct { StoriesExtMeta - ID int `json:"id"` - Parent any `json:"parent"` // 可能是数组, 产品计划时是数组 - Product int `json:"product"` - Branch int `json:"branch"` - Module int `json:"module"` - Plan string `json:"plan"` - Frombug int `json:"fromBug"` - Title string `json:"title"` - Type string `json:"type"` - Status string `json:"status"` - Substatus string `json:"subStatus"` - Color string `json:"color"` - Stage string `json:"stage"` - Stagedby string `json:"stagedBy"` - Mailto string `json:"mailto"` - Openedby any `json:"openedBy"` // 产品计划是string, 其他可能是UserMeta - Openeddate string `json:"openedDate"` - Assignedto string `json:"assignedTo"` - Assigneddate any `json:"assignedDate"` - Lasteditedby string `json:"lastEditedBy"` - Lastediteddate string `json:"lastEditedDate"` - Reviewedby string `json:"reviewedBy"` - Revieweddate any `json:"reviewedDate"` - Closedby string `json:"closedBy"` - Closeddate any `json:"closedDate"` - Closedreason string `json:"closedReason"` - Tobug int `json:"toBug"` - Childstories string `json:"childStories"` - Linkstories string `json:"linkStories"` - Duplicatestory int `json:"duplicateStory"` - Version int `json:"version"` - Urchanged string `json:"URChanged"` - Deleted string `json:"deleted"` - Plantitle string `json:"planTitle,omitempty"` + ID int `json:"id"` + Product int `json:"product"` + Project int `json:"project,omitempty"` + Branch int `json:"branch"` + Module int `json:"module"` + Plan string `json:"plan"` + Frombug int `json:"fromBug"` + Title string `json:"title"` + Type string `json:"type"` + Status StoriesStatus `json:"status"` + Substatus string `json:"subStatus"` + Color string `json:"color"` + Stage StoriesStage `json:"stage"` + Stagedby any `json:"stagedBy"` + Mailto any `json:"mailto"` // 概率数组 + Openedby any `json:"openedBy"` // 产品计划是string, 其他可能是UserMeta + Openeddate string `json:"openedDate"` + Assignedto any `json:"assignedTo"` + Assigneddate string `json:"assignedDate"` + Lasteditedby any `json:"lastEditedBy"` + Lastediteddate string `json:"lastEditedDate"` + Reviewedby any `json:"reviewedBy"` + Revieweddate string `json:"reviewedDate"` + Closedby any `json:"closedBy"` + Closeddate string `json:"closedDate"` + Closedreason string `json:"closedReason"` + Tobug int `json:"toBug"` + Childstories string `json:"childStories"` + Linkstories string `json:"linkStories"` + Duplicatestory int `json:"duplicateStory"` + Version int `json:"version"` + Urchanged string `json:"URChanged"` + Deleted any `json:"deleted"` // 需求返回时是bool, 产品计划是string + Plantitle string `json:"planTitle,omitempty"` + NotReview []string `json:"notReview,omitempty"` + NeedSummaryEstimate bool `json:"needSummaryEstimate,omitempty"` + ProductStatus string `json:"productStatus,omitempty"` } type StoriesCreateMeta struct { @@ -104,7 +109,18 @@ type StoriesMsg struct { Executions []interface{} `json:"executions"` Tasks []interface{} `json:"tasks"` Stages []interface{} `json:"stages"` - Children []interface{} `json:"children"` + Children []interface{} `json:"children,omitempty"` +} + +type StoriesClose struct { + Closedreason StoriesCloseReason `json:"closedReason,omitempty"` + Duplicatestory int `json:"duplicateStory,omitempty"` + Comment string `json:"comment,omitempty"` +} + +type StoriesActive struct { + AssignedTo string `json:"assignedTo,omitempty"` + Comment string `json:"comment,omitempty"` } // ProjectsList 获取项目需求列表 @@ -148,17 +164,6 @@ func (s *StoriesService) DeleteByID(id int) (*CustomResp, *req.Response, error) return &u, resp, err } -// UpdateByID 变更需求 -func (s *StoriesService) UpdateByID(id int, story StoriesMeta) (*StoriesMsg, *req.Response, error) { - var u StoriesMsg - resp, err := s.client.client.R(). - SetHeader("Token", s.client.token). - SetBody(&story). - SetSuccessResult(&u). - Post(s.client.RequestURL(fmt.Sprintf("/stories/%d/change", id))) - return &u, resp, err -} - // GetByID 获取需求详情 func (s *StoriesService) GetByID(id int) (*StoriesMsg, *req.Response, error) { var u StoriesMsg @@ -189,3 +194,91 @@ func (s *StoriesService) UpdateFieldByID(id int, uf StoriesUpdateFieldMeta) (*St Put(s.client.RequestURL(fmt.Sprintf("/stories/%d", id))) return &u, resp, err } + +// UpdateByID 变更需求 +func (s *StoriesService) UpdateByID(id int, story StoriesMeta) (*StoriesMsg, *req.Response, error) { + var u StoriesMsg + resp, err := s.client.client.R(). + SetHeader("Token", s.client.token). + SetBody(&story). + SetSuccessResult(&u). + Post(s.client.RequestURL(fmt.Sprintf("/stories/%d/change", id))) + return &u, resp, err +} + +// CloseByID 关闭需求 +func (s *StoriesService) CloseByID(id int, story StoriesClose) (*StoriesMsg, *req.Response, error) { + var u StoriesMsg + resp, err := s.client.client.R(). + SetHeader("Token", s.client.token). + SetBody(&story). + SetSuccessResult(&u). + Post(s.client.RequestURL(fmt.Sprintf("/stories/%d/close", id))) + return &u, resp, err +} + +// ActiveByID 激活需求 +func (s *StoriesService) ActiveByID(id int, story StoriesActive) (*StoriesMsg, *req.Response, error) { + var u StoriesMsg + resp, err := s.client.client.R(). + SetHeader("Token", s.client.token). + SetBody(&story). + SetSuccessResult(&u). + Post(s.client.RequestURL(fmt.Sprintf("/stories/%d/active", id))) + return &u, resp, err +} + +// AssignByID 指派需求 +func (s *StoriesService) AssignByID(id int, story StoriesActive) (*StoriesMsg, *req.Response, error) { + var u StoriesMsg + resp, err := s.client.client.R(). + SetHeader("Token", s.client.token). + SetBody(&story). + SetSuccessResult(&u). + Post(s.client.RequestURL(fmt.Sprintf("/stories/%d/assign", id))) + return &u, resp, err +} + +// EstimateByID 预估工时 +func (s *StoriesService) EstimateByID(id int, story StoriesMeta) (*StoriesMsg, *req.Response, error) { + var u StoriesMsg + resp, err := s.client.client.R(). + SetHeader("Token", s.client.token). + SetBody(&story). + SetSuccessResult(&u). + Post(s.client.RequestURL(fmt.Sprintf("/stories/%d/estimate", id))) + return &u, resp, err +} + +// ChildByID 子需求 +func (s *StoriesService) ChildByID(id int, story StoriesMeta) (*StoriesMsg, *req.Response, error) { + var u StoriesMsg + resp, err := s.client.client.R(). + SetHeader("Token", s.client.token). + SetBody(&story). + SetSuccessResult(&u). + Post(s.client.RequestURL(fmt.Sprintf("/stories/%d/child", id))) + return &u, resp, err +} + +// RecallByID 撤回评审 +func (s *StoriesService) RecallByID(id int, story StoriesMeta) (*StoriesMsg, *req.Response, error) { + var u StoriesMsg + resp, err := s.client.client.R(). + SetHeader("Token", s.client.token). + SetBody(&story). + SetSuccessResult(&u). + Post(s.client.RequestURL(fmt.Sprintf("/stories/%d/recall", id))) + return &u, resp, err +} + +// ReviewByID 审核需求 +func (s *StoriesService) ReviewByID(id int, story StoriesMeta) (*StoriesMsg, *req.Response, error) { + var u StoriesMsg + resp, err := s.client.client.R(). + SetHeader("Token", s.client.token). + SetBody(&story). + SetSuccessResult(&u). + Post(s.client.RequestURL(fmt.Sprintf("/stories/%d/review", id))) + return &u, resp, err +} diff --git a/zentao/types.go b/zentao/types.go index 5f81573..a85b9dd 100644 --- a/zentao/types.go +++ b/zentao/types.go @@ -38,6 +38,62 @@ var ( ACLOpen ACL = "open" // 公开 ) +type StoriesSource string // 需求来源 + +var ( + SourceCustomer StoriesSource = "customer" // 客户 + SourceMarket StoriesSource = "market" // 市场 + SourceProduct StoriesSource = "po" // 产品 + SourceUser StoriesSource = "user" // 用户 +) + +type StoriesStage string // 需求状态 + +var ( + StageWait StoriesStage = "wait" // 未开始 + StagePlanned StoriesStage = "planned" // 已计划 + StageProjected StoriesStage = "projected" // 已立项 + StageDeveloping StoriesStage = "developing" // 开发中 + StageDeveloped StoriesStage = "developed" // 研发完毕 + StageTesting StoriesStage = "testing" // 测试中 + StageTested StoriesStage = "tested" // 测试完毕 + StageVerified StoriesStage = "verified" // 已验收 + StageReleased StoriesStage = "released" // 已发布 + StageClosed StoriesStage = "closed" // 已关闭 +) + +type StoriesCategory string // 需求分类 + +var ( + CategoryFeature StoriesCategory = "feature" // 功能 + CategoryInterface StoriesCategory = "interface" // 接口 + CategoryPerformance StoriesCategory = "performance" // 性能 + CategorySafe StoriesCategory = "safe" // 安全 + CategoryExperience StoriesCategory = "experience" // 体验 + CategoryImprove StoriesCategory = "improve" // 改进 + CategoryOther StoriesCategory = "other" // 其他 +) + +type StoriesStatus string // 需求状态 + +var ( + StatusDraft StoriesStatus = "draft" // 草稿 + StatusActive StoriesStatus = "active" // 激活 + StatusClosed StoriesStatus = "closed" // 关闭 + StatusChanged StoriesStatus = "changed" // 已变更 +) + +type StoriesCloseReason string // 需求关闭原因 + +var ( + CloseReasonDone StoriesCloseReason = "done" // 完成 + CloseReasonDuplicate StoriesCloseReason = "duplicate" // 重复 + CloseReasonPostponed StoriesCloseReason = "postponed" // 延期 + CloseReasonwillnotdo StoriesCloseReason = "willnotdo" // 不做 + CloseReasonCancel StoriesCloseReason = "cancel" // 取消 + CloseReasonBydesign StoriesCloseReason = "bydesign" // 设计如此 +) + // CustomResp 通用Resp type CustomResp struct { Message string `json:"message,omitempty"`