diff --git a/examples/renderedfields/main.go b/examples/renderedfields/main.go new file mode 100644 index 0000000..ab23420 --- /dev/null +++ b/examples/renderedfields/main.go @@ -0,0 +1,66 @@ +package main + +import ( + "bufio" + "fmt" + "net/http" + "os" + "strings" + "syscall" + + jira "github.com/andygrunwald/go-jira" + "golang.org/x/crypto/ssh/terminal" +) + +func main() { + r := bufio.NewReader(os.Stdin) + + fmt.Print("Jira URL: ") + jiraURL, _ := r.ReadString('\n') + + fmt.Print("Jira Issue key: ") + key, _ := r.ReadString('\n') + key = strings.TrimSpace(key) + + fmt.Print("Jira Username: ") + username, _ := r.ReadString('\n') + + fmt.Print("Jira Password: ") + bytePassword, _ := terminal.ReadPassword(int(syscall.Stdin)) + password := string(bytePassword) + + var tp *http.Client + + if strings.TrimSpace(username) == "" { + tp = nil + } else { + + ba := jira.BasicAuthTransport{ + Username: strings.TrimSpace(username), + Password: strings.TrimSpace(password), + } + tp = ba.Client() + } + + client, err := jira.NewClient(tp, strings.TrimSpace(jiraURL)) + if err != nil { + fmt.Printf("\nerror: %v\n", err) + return + } + + fmt.Printf("Targetting %s for issue %s\n", strings.TrimSpace(jiraURL), key) + + options := &jira.GetQueryOptions{Expand: "renderedFields"} + u, _, err := client.Issue.Get(key, options) + + if err != nil { + fmt.Printf("\n==> error: %v\n", err) + return + } + + fmt.Printf("RenderedFields: %+v\n", *u.RenderedFields) + + for _, c := range u.RenderedFields.Comments.Comments { + fmt.Printf(" %+v\n", c) + } +} diff --git a/issue.go b/issue.go index c29b7b0..0c57b5a 100644 --- a/issue.go +++ b/issue.go @@ -31,12 +31,13 @@ type IssueService struct { // Issue represents a JIRA issue. type Issue struct { - Expand string `json:"expand,omitempty" structs:"expand,omitempty"` - ID string `json:"id,omitempty" structs:"id,omitempty"` - Self string `json:"self,omitempty" structs:"self,omitempty"` - Key string `json:"key,omitempty" structs:"key,omitempty"` - Fields *IssueFields `json:"fields,omitempty" structs:"fields,omitempty"` - Changelog *Changelog `json:"changelog,omitempty" structs:"changelog,omitempty"` + Expand string `json:"expand,omitempty" structs:"expand,omitempty"` + ID string `json:"id,omitempty" structs:"id,omitempty"` + Self string `json:"self,omitempty" structs:"self,omitempty"` + Key string `json:"key,omitempty" structs:"key,omitempty"` + Fields *IssueFields `json:"fields,omitempty" structs:"fields,omitempty"` + RenderedFields *IssueRenderedFields `json:"renderedFields,omitempty" structs:"renderedFields,omitempty"` + Changelog *Changelog `json:"changelog,omitempty" structs:"changelog,omitempty"` } // ChangelogItems reflects one single changelog item of a history item @@ -196,6 +197,23 @@ func (i *IssueFields) UnmarshalJSON(data []byte) error { } +// IssueRenderedFields represents rendered fields of a JIRA issue. +// Not all IssueFields are rendered. +type IssueRenderedFields struct { + // TODO Missing fields + // * "aggregatetimespent": null, + // * "workratio": -1, + // * "lastViewed": null, + // * "aggregatetimeoriginalestimate": null, + // * "aggregatetimeestimate": null, + // * "environment": null, + Resolutiondate string `json:"resolutiondate,omitempty" structs:"resolutiondate,omitempty"` + Created string `json:"created,omitempty" structs:"created,omitempty"` + Duedate string `json:"duedate,omitempty" structs:"duedate,omitempty"` + Updated string `json:"updated,omitempty" structs:"updated,omitempty"` + Comments *Comments `json:"comment,omitempty" structs:"comment,omitempty"` +} + // IssueType represents a type of a JIRA issue. // Typical types are "Request", "Bug", "Story", ... type IssueType struct { diff --git a/issue_test.go b/issue_test.go index 453d664..8a378b7 100644 --- a/issue_test.go +++ b/issue_test.go @@ -273,6 +273,37 @@ func TestIssueService_Get_Fields(t *testing.T) { } } +func TestIssueService_Get_RenderedFields(t *testing.T) { + setup() + defer teardown() + testMux.HandleFunc("/rest/api/2/issue/10002", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + testRequestURL(t, r, "/rest/api/2/issue/10002") + + fmt.Fprint(w, `{"expand":"renderedFields,names,schema,transitions,operations,editmeta,changelog,versionedRepresentations","id":"10002","self":"http://www.example.com/jira/rest/api/2/issue/10002","key":"EX-1","fields":{"labels":["test"],"watcher":{"self":"http://www.example.com/jira/rest/api/2/issue/EX-1/watchers","isWatching":false,"watchCount":1,"watchers":[{"self":"http://www.example.com/jira/rest/api/2/user?username=fred","name":"fred","displayName":"Fred F. User","active":false}]},"epic": {"id": 19415,"key": "EPIC-77","self": "https://example.atlassian.net/rest/agile/1.0/epic/19415","name": "Epic Name","summary": "Do it","color": {"key": "color_11"},"done": false},"attachment":[{"self":"http://www.example.com/jira/rest/api/2.0/attachments/10000","filename":"picture.jpg","author":{"self":"http://www.example.com/jira/rest/api/2/user?username=fred","name":"fred","avatarUrls":{"48x48":"http://www.example.com/jira/secure/useravatar?size=large&ownerId=fred","24x24":"http://www.example.com/jira/secure/useravatar?size=small&ownerId=fred","16x16":"http://www.example.com/jira/secure/useravatar?size=xsmall&ownerId=fred","32x32":"http://www.example.com/jira/secure/useravatar?size=medium&ownerId=fred"},"displayName":"Fred F. User","active":false},"created":"2016-03-16T04:22:37.461+0000","size":23123,"mimeType":"image/jpeg","content":"http://www.example.com/jira/attachments/10000","thumbnail":"http://www.example.com/jira/secure/thumbnail/10000"}],"sub-tasks":[{"id":"10000","type":{"id":"10000","name":"","inward":"Parent","outward":"Sub-task"},"outwardIssue":{"id":"10003","key":"EX-2","self":"http://www.example.com/jira/rest/api/2/issue/EX-2","fields":{"status":{"iconUrl":"http://www.example.com/jira//images/icons/statuses/open.png","name":"Open"}}}}],"description":"example bug report","project":{"self":"http://www.example.com/jira/rest/api/2/project/EX","id":"10000","key":"EX","name":"Example","avatarUrls":{"48x48":"http://www.example.com/jira/secure/projectavatar?size=large&pid=10000","24x24":"http://www.example.com/jira/secure/projectavatar?size=small&pid=10000","16x16":"http://www.example.com/jira/secure/projectavatar?size=xsmall&pid=10000","32x32":"http://www.example.com/jira/secure/projectavatar?size=medium&pid=10000"},"projectCategory":{"self":"http://www.example.com/jira/rest/api/2/projectCategory/10000","id":"10000","name":"FIRST","description":"First Project Category"}},"comment":{"comments":[{"self":"http://www.example.com/jira/rest/api/2/issue/10010/comment/10000","id":"10000","author":{"self":"http://www.example.com/jira/rest/api/2/user?username=fred","name":"fred","displayName":"Fred F. User","active":false},"body":"Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque eget venenatis elit. Duis eu justo eget augue iaculis fermentum. Sed semper quam laoreet nisi egestas at posuere augue semper.","updateAuthor":{"self":"http://www.example.com/jira/rest/api/2/user?username=fred","name":"fred","displayName":"Fred F. User","active":false},"created":"2016-03-16T04:22:37.356+0000","updated":"2016-03-16T04:22:37.356+0000","visibility":{"type":"role","value":"Administrators"}}]},"issuelinks":[{"id":"10001","type":{"id":"10000","name":"Dependent","inward":"depends on","outward":"is depended by"},"outwardIssue":{"id":"10004L","key":"PRJ-2","self":"http://www.example.com/jira/rest/api/2/issue/PRJ-2","fields":{"status":{"iconUrl":"http://www.example.com/jira//images/icons/statuses/open.png","name":"Open"}}}},{"id":"10002","type":{"id":"10000","name":"Dependent","inward":"depends on","outward":"is depended by"},"inwardIssue":{"id":"10004","key":"PRJ-3","self":"http://www.example.com/jira/rest/api/2/issue/PRJ-3","fields":{"status":{"iconUrl":"http://www.example.com/jira//images/icons/statuses/open.png","name":"Open"}}}}],"worklog":{"worklogs":[{"self":"http://www.example.com/jira/rest/api/2/issue/10010/worklog/10000","author":{"self":"http://www.example.com/jira/rest/api/2/user?username=fred","name":"fred","displayName":"Fred F. User","active":false},"updateAuthor":{"self":"http://www.example.com/jira/rest/api/2/user?username=fred","name":"fred","displayName":"Fred F. User","active":false},"comment":"I did some work here.","updated":"2016-03-16T04:22:37.471+0000","visibility":{"type":"group","value":"jira-developers"},"started":"2016-03-16T04:22:37.471+0000","timeSpent":"3h 20m","timeSpentSeconds":12000,"id":"100028","issueId":"10002"}]},"updated":"2016-04-06T02:36:53.594-0700","duedate":"2018-01-19","timetracking":{"originalEstimate":"10m","remainingEstimate":"3m","timeSpent":"6m","originalEstimateSeconds":600,"remainingEstimateSeconds":200,"timeSpentSeconds":400}},"names":{"watcher":"watcher","attachment":"attachment","sub-tasks":"sub-tasks","description":"description","project":"project","comment":"comment","issuelinks":"issuelinks","worklog":"worklog","updated":"updated","timetracking":"timetracking"},"schema":{},"renderedFields":{"resolutiondate":"In 1 week","updated":"2 hours ago","comment":{"comments":[{"body":"This is HTML"}]}}}`) + }) + + issue, _, err := testClient.Issue.Get("10002", nil) + if issue == nil { + t.Error("Expected issue. Issue is nil") + } + if issue.RenderedFields.Updated != "2 hours ago" { + t.Error("Expected updated to equla '2 hours ago' for rendered field") + } + + if len(issue.RenderedFields.Comments.Comments) != 1 { + t.Errorf("Expected one comment, %v found", len(issue.RenderedFields.Comments.Comments)) + } + comment := issue.RenderedFields.Comments.Comments[0] + if comment.Body != "This is HTML" { + t.Errorf("Wrong comment body returned in RenderedField. Got %s", comment.Body) + } + + if err != nil { + t.Errorf("Error given: %s", err) + } +} + func TestIssueService_DownloadAttachment(t *testing.T) { var testAttachment = "Here is an attachment"