grim/youtrack-import

ff8d512f4f72
Add support for auto linking tickets. Fixes YI-31
package youtrack
import (
"bytes"
"encoding/xml"
"fmt"
"net/http"
"time"
)
type Issue struct {
Number int
Summary string
Description string
Created time.Time
Updated time.Time
UpdatedBy string
Resolved time.Time
Reporter string
Assignee string
Voters []string
Watchers []string
PermittedGroup string
Markdown bool
AffectedVersion string
FixVersion string
Subsystem string
Priority string
Type string
State string
Comments []*Comment
Attachments []*Attachment
}
func (i *Issue) extractAttachments() error {
if i.Markdown {
modified, attachments, err := extractAttachments(
i.Number,
i.Reporter,
i.Description,
i.Created,
)
if err != nil {
return err
}
if len(attachments) > 0 {
i.Description = modified
i.Attachments = append(i.Attachments, attachments...)
}
}
for _, comment := range i.Comments {
if err := comment.extractAttachments(i); err != nil {
return err
}
}
return nil
}
func (i *Issue) encode() xmlIssue {
x := xmlIssue{}
// required fields
x.AddField("numberInProject", fmt.Sprintf("%d", i.Number))
x.AddField("summary", i.Summary)
x.AddField("reporterName", i.Reporter)
x.AddField("Assignee", i.Assignee)
x.AddField("Type", i.Type)
x.AddField("Priority", i.Priority)
x.AddField("State", i.State)
// optional fields
if i.Markdown {
x.AddField("markdown", "true")
}
if !i.Created.IsZero() {
x.AddField("created", formatTimeString(i.Created))
}
if i.Description != "" {
x.AddField("description", i.Description)
}
if !i.Updated.IsZero() {
x.AddField("updated", formatTimeString(i.Updated))
}
if i.UpdatedBy != "" {
x.AddField("updaterName", i.UpdatedBy)
}
if !i.Resolved.IsZero() {
x.AddField("resolved", fmt.Sprintf("%d", i.Resolved.Unix()))
}
if len(i.Voters) > 0 {
x.AddFieldSlice("voterName", i.Voters)
}
if len(i.Watchers) > 0 {
x.AddFieldSlice("watcherName", i.Watchers)
}
if i.PermittedGroup != "" {
x.AddField("permittedGroup", i.PermittedGroup)
}
if i.AffectedVersion != "" {
x.AddField("Affected Versions", i.AffectedVersion)
}
if i.FixVersion != "" {
x.AddField("Fix Versions", i.FixVersion)
}
if i.Subsystem != "" {
x.AddField("Subsystem", i.Subsystem)
}
sortComments(i.Comments)
for _, c := range i.Comments {
x.AddComment(c.Author, c.Text, c.Created, c.Updated, c.Markdown)
}
return x
}
func (c *Client) importIssuesRange(p *Project, s, e int) error {
issues := &xmlIssues{}
issues.Issues = make([]xmlIssue, e-s)
for i := s; i < e; i++ {
if err := p.Issues[i].extractAttachments(); err != nil {
return err
}
issues.Issues[i-s] = p.Issues[i].encode()
}
// convert the datastructure to an xml string
data, err := xml.Marshal(issues)
if err != nil {
return err
}
// create the reader for the put request
putBody := bytes.NewReader(data)
resp, err := c.xmlRequest(
http.MethodPut,
c.uri+"/import/"+p.ID+"/issues",
putBody,
)
if err != nil {
return err
}
defer resp.Body.Close()
return c.validateXmlImportReport(
[]int{http.StatusOK, http.StatusBadRequest},
resp,
)
}
func (c *Client) ImportIssues(p *Project) error {
r := 10
for s := 0; s < len(p.Issues); s += r {
e := s + r
if e > len(p.Issues) {
e = len(p.Issues)
}
fmt.Printf("importing issues %d-%d ... ", s, e-1)
if err := c.importIssuesRange(p, s, e); err != nil {
fmt.Printf("failed.\n")
return err
}
fmt.Printf("done.\n")
}
return nil
}