1
0
Fork 0
mirror of https://github.com/documize/community.git synced 2025-08-02 03:55:24 +02:00

Update Jira client library to latest release

Related to #308
This commit is contained in:
HarveyKandola 2019-08-27 17:16:06 +01:00
parent 5b72da037c
commit 0419f3b7b3
18 changed files with 1734 additions and 1274 deletions

View file

@ -7,6 +7,7 @@ import (
"io"
"io/ioutil"
"mime/multipart"
"net/http"
"net/url"
"reflect"
"strings"
@ -29,6 +30,13 @@ type IssueService struct {
client *Client
}
// UpdateQueryOptions specifies the optional parameters to the Edit issue
type UpdateQueryOptions struct {
NotifyUsers bool `url:"notifyUsers,omitempty"`
OverrideScreenSecurity bool `url:"overrideScreenSecurity,omitempty"`
OverrideEditableFlag bool `url:"overrideEditableFlag,omitempty"`
}
// Issue represents a JIRA issue.
type Issue struct {
Expand string `json:"expand,omitempty" structs:"expand,omitempty"`
@ -91,46 +99,47 @@ type Epic struct {
// Every JIRA issue has several fields attached.
type IssueFields struct {
// TODO Missing fields
// * "aggregatetimespent": null,
// * "workratio": -1,
// * "lastViewed": null,
// * "aggregatetimeoriginalestimate": null,
// * "aggregatetimeestimate": null,
// * "environment": null,
Expand string `json:"expand,omitempty" structs:"expand,omitempty"`
Type IssueType `json:"issuetype,omitempty" structs:"issuetype,omitempty"`
Project Project `json:"project,omitempty" structs:"project,omitempty"`
Resolution *Resolution `json:"resolution,omitempty" structs:"resolution,omitempty"`
Priority *Priority `json:"priority,omitempty" structs:"priority,omitempty"`
Resolutiondate Time `json:"resolutiondate,omitempty" structs:"resolutiondate,omitempty"`
Created Time `json:"created,omitempty" structs:"created,omitempty"`
Duedate Date `json:"duedate,omitempty" structs:"duedate,omitempty"`
Watches *Watches `json:"watches,omitempty" structs:"watches,omitempty"`
Assignee *User `json:"assignee,omitempty" structs:"assignee,omitempty"`
Updated Time `json:"updated,omitempty" structs:"updated,omitempty"`
Description string `json:"description,omitempty" structs:"description,omitempty"`
Summary string `json:"summary,omitempty" structs:"summary,omitempty"`
Creator *User `json:"Creator,omitempty" structs:"Creator,omitempty"`
Reporter *User `json:"reporter,omitempty" structs:"reporter,omitempty"`
Components []*Component `json:"components,omitempty" structs:"components,omitempty"`
Status *Status `json:"status,omitempty" structs:"status,omitempty"`
Progress *Progress `json:"progress,omitempty" structs:"progress,omitempty"`
AggregateProgress *Progress `json:"aggregateprogress,omitempty" structs:"aggregateprogress,omitempty"`
TimeTracking *TimeTracking `json:"timetracking,omitempty" structs:"timetracking,omitempty"`
TimeSpent int `json:"timespent,omitempty" structs:"timespent,omitempty"`
TimeEstimate int `json:"timeestimate,omitempty" structs:"timeestimate,omitempty"`
TimeOriginalEstimate int `json:"timeoriginalestimate,omitempty" structs:"timeoriginalestimate,omitempty"`
Worklog *Worklog `json:"worklog,omitempty" structs:"worklog,omitempty"`
IssueLinks []*IssueLink `json:"issuelinks,omitempty" structs:"issuelinks,omitempty"`
Comments *Comments `json:"comment,omitempty" structs:"comment,omitempty"`
FixVersions []*FixVersion `json:"fixVersions,omitempty" structs:"fixVersions,omitempty"`
Labels []string `json:"labels,omitempty" structs:"labels,omitempty"`
Subtasks []*Subtasks `json:"subtasks,omitempty" structs:"subtasks,omitempty"`
Attachments []*Attachment `json:"attachment,omitempty" structs:"attachment,omitempty"`
Epic *Epic `json:"epic,omitempty" structs:"epic,omitempty"`
Sprint *Sprint `json:"sprint,omitempty" structs:"sprint,omitempty"`
Parent *Parent `json:"parent,omitempty" structs:"parent,omitempty"`
Unknowns tcontainer.MarshalMap
Expand string `json:"expand,omitempty" structs:"expand,omitempty"`
Type IssueType `json:"issuetype,omitempty" structs:"issuetype,omitempty"`
Project Project `json:"project,omitempty" structs:"project,omitempty"`
Resolution *Resolution `json:"resolution,omitempty" structs:"resolution,omitempty"`
Priority *Priority `json:"priority,omitempty" structs:"priority,omitempty"`
Resolutiondate Time `json:"resolutiondate,omitempty" structs:"resolutiondate,omitempty"`
Created Time `json:"created,omitempty" structs:"created,omitempty"`
Duedate Date `json:"duedate,omitempty" structs:"duedate,omitempty"`
Watches *Watches `json:"watches,omitempty" structs:"watches,omitempty"`
Assignee *User `json:"assignee,omitempty" structs:"assignee,omitempty"`
Updated Time `json:"updated,omitempty" structs:"updated,omitempty"`
Description string `json:"description,omitempty" structs:"description,omitempty"`
Summary string `json:"summary,omitempty" structs:"summary,omitempty"`
Creator *User `json:"Creator,omitempty" structs:"Creator,omitempty"`
Reporter *User `json:"reporter,omitempty" structs:"reporter,omitempty"`
Components []*Component `json:"components,omitempty" structs:"components,omitempty"`
Status *Status `json:"status,omitempty" structs:"status,omitempty"`
Progress *Progress `json:"progress,omitempty" structs:"progress,omitempty"`
AggregateProgress *Progress `json:"aggregateprogress,omitempty" structs:"aggregateprogress,omitempty"`
TimeTracking *TimeTracking `json:"timetracking,omitempty" structs:"timetracking,omitempty"`
TimeSpent int `json:"timespent,omitempty" structs:"timespent,omitempty"`
TimeEstimate int `json:"timeestimate,omitempty" structs:"timeestimate,omitempty"`
TimeOriginalEstimate int `json:"timeoriginalestimate,omitempty" structs:"timeoriginalestimate,omitempty"`
Worklog *Worklog `json:"worklog,omitempty" structs:"worklog,omitempty"`
IssueLinks []*IssueLink `json:"issuelinks,omitempty" structs:"issuelinks,omitempty"`
Comments *Comments `json:"comment,omitempty" structs:"comment,omitempty"`
FixVersions []*FixVersion `json:"fixVersions,omitempty" structs:"fixVersions,omitempty"`
AffectsVersions []*AffectsVersion `json:"versions,omitempty" structs:"versions,omitempty"`
Labels []string `json:"labels,omitempty" structs:"labels,omitempty"`
Subtasks []*Subtasks `json:"subtasks,omitempty" structs:"subtasks,omitempty"`
Attachments []*Attachment `json:"attachment,omitempty" structs:"attachment,omitempty"`
Epic *Epic `json:"epic,omitempty" structs:"epic,omitempty"`
Sprint *Sprint `json:"sprint,omitempty" structs:"sprint,omitempty"`
Parent *Parent `json:"parent,omitempty" structs:"parent,omitempty"`
AggregateTimeOriginalEstimate int `json:"aggregatetimeoriginalestimate,omitempty" structs:"aggregatetimeoriginalestimate,omitempty"`
AggregateTimeSpent int `json:"aggregatetimespent,omitempty" structs:"aggregatetimespent,omitempty"`
AggregateTimeEstimate int `json:"aggregatetimeestimate,omitempty" structs:"aggregatetimeestimate,omitempty"`
Unknowns tcontainer.MarshalMap
}
// MarshalJSON is a custom JSON marshal function for the IssueFields structs.
@ -212,6 +221,7 @@ type IssueRenderedFields struct {
Duedate string `json:"duedate,omitempty" structs:"duedate,omitempty"`
Updated string `json:"updated,omitempty" structs:"updated,omitempty"`
Comments *Comments `json:"comment,omitempty" structs:"comment,omitempty"`
Description string `json:"description,omitempty" structs:"description,omitempty"`
}
// IssueType represents a type of a JIRA issue.
@ -274,6 +284,7 @@ type Status struct {
type Progress struct {
Progress int `json:"progress" structs:"progress"`
Total int `json:"total" structs:"total"`
Percent int `json:"percent" structs:"percent"`
}
// Parent represents the parent of a JIRA issue, to be used with subtask issue types.
@ -285,6 +296,10 @@ type Parent struct {
// Time represents the Time definition of JIRA as a time.Time of go
type Time time.Time
func (t Time) Equal(u Time) bool {
return time.Time(t).Equal(time.Time(u))
}
// Date represents the Date definition of JIRA as a time.Time of go
type Date time.Time
@ -343,6 +358,12 @@ func (t *Time) UnmarshalJSON(b []byte) error {
return nil
}
// MarshalJSON will transform the time.Time into a JIRA time
// during the creation of a JIRA request
func (t Time) MarshalJSON() ([]byte, error) {
return []byte(time.Time(t).Format("\"2006-01-02T15:04:05.999-0700\"")), nil
}
// UnmarshalJSON will transform the JIRA date into a time.Time
// during the transformation of the JIRA JSON response
func (t *Date) UnmarshalJSON(b []byte) error {
@ -378,17 +399,23 @@ type Worklog struct {
// WorklogRecord represents one entry of a Worklog
type WorklogRecord struct {
Self string `json:"self,omitempty" structs:"self,omitempty"`
Author *User `json:"author,omitempty" structs:"author,omitempty"`
UpdateAuthor *User `json:"updateAuthor,omitempty" structs:"updateAuthor,omitempty"`
Comment string `json:"comment,omitempty" structs:"comment,omitempty"`
Created *Time `json:"created,omitempty" structs:"created,omitempty"`
Updated *Time `json:"updated,omitempty" structs:"updated,omitempty"`
Started *Time `json:"started,omitempty" structs:"started,omitempty"`
TimeSpent string `json:"timeSpent,omitempty" structs:"timeSpent,omitempty"`
TimeSpentSeconds int `json:"timeSpentSeconds,omitempty" structs:"timeSpentSeconds,omitempty"`
ID string `json:"id,omitempty" structs:"id,omitempty"`
IssueID string `json:"issueId,omitempty" structs:"issueId,omitempty"`
Self string `json:"self,omitempty" structs:"self,omitempty"`
Author *User `json:"author,omitempty" structs:"author,omitempty"`
UpdateAuthor *User `json:"updateAuthor,omitempty" structs:"updateAuthor,omitempty"`
Comment string `json:"comment,omitempty" structs:"comment,omitempty"`
Created *Time `json:"created,omitempty" structs:"created,omitempty"`
Updated *Time `json:"updated,omitempty" structs:"updated,omitempty"`
Started *Time `json:"started,omitempty" structs:"started,omitempty"`
TimeSpent string `json:"timeSpent,omitempty" structs:"timeSpent,omitempty"`
TimeSpentSeconds int `json:"timeSpentSeconds,omitempty" structs:"timeSpentSeconds,omitempty"`
ID string `json:"id,omitempty" structs:"id,omitempty"`
IssueID string `json:"issueId,omitempty" structs:"issueId,omitempty"`
Properties []EntityProperty `json:"properties,omitempty"`
}
type EntityProperty struct {
Key string `json:"key"`
Value interface{} `json:"value"`
}
// TimeTracking represents the timetracking fields of a JIRA issue.
@ -449,16 +476,21 @@ type Comment struct {
// FixVersion represents a software release in which an issue is fixed.
type FixVersion struct {
Archived *bool `json:"archived,omitempty" structs:"archived,omitempty"`
Self string `json:"self,omitempty" structs:"self,omitempty"`
ID string `json:"id,omitempty" structs:"id,omitempty"`
Name string `json:"name,omitempty" structs:"name,omitempty"`
ProjectID int `json:"projectId,omitempty" structs:"projectId,omitempty"`
ReleaseDate string `json:"releaseDate,omitempty" structs:"releaseDate,omitempty"`
Description string `json:"description,omitempty" structs:"name,omitempty"`
Archived *bool `json:"archived,omitempty" structs:"archived,omitempty"`
Released *bool `json:"released,omitempty" structs:"released,omitempty"`
Self string `json:"self,omitempty" structs:"self,omitempty"`
ReleaseDate string `json:"releaseDate,omitempty" structs:"releaseDate,omitempty"`
UserReleaseDate string `json:"userReleaseDate,omitempty" structs:"userReleaseDate,omitempty"`
ProjectID int `json:"projectId,omitempty" structs:"projectId,omitempty"` // Unlike other IDs, this is returned as a number
StartDate string `json:"startDate,omitempty" structs:"startDate,omitempty"`
}
// AffectsVersion represents a software release which is affected by an issue.
type AffectsVersion Version
// CommentVisibility represents he visibility of a comment.
// E.g. Type could be "role" and Value "Administrators"
type CommentVisibility struct {
@ -506,6 +538,22 @@ type GetQueryOptions struct {
ProjectKeys string `url:"projectKeys,omitempty"`
}
// GetWorklogsQueryOptions specifies the optional parameters for the Get Worklogs method
type GetWorklogsQueryOptions struct {
StartAt int64 `url:"startAt,omitempty"`
MaxResults int32 `url:"maxResults,omitempty"`
Expand string `url:"expand,omitempty"`
}
type AddWorklogQueryOptions struct {
NotifyUsers bool `url:"notifyUsers,omitempty"`
AdjustEstimate string `url:"adjustEstimate,omitempty"`
NewEstimate string `url:"newEstimate,omitempty"`
ReduceBy string `url:"reduceBy,omitempty"`
Expand string `url:"expand,omitempty"`
OverrideEditableFlag bool `url:"overrideEditableFlag,omitempty"`
}
// CustomFields represents custom fields of JIRA
// This can heavily differ between JIRA instances
type CustomFields map[string]string
@ -601,11 +649,29 @@ func (s *IssueService) PostAttachment(issueID string, r io.Reader, attachmentNam
return attachment, resp, nil
}
// DeleteAttachment deletes an attachment of a given attachmentID
func (s *IssueService) DeleteAttachment(attachmentID string) (*Response, error) {
apiEndpoint := fmt.Sprintf("rest/api/2/attachment/%s", attachmentID)
req, err := s.client.NewRequest("DELETE", apiEndpoint, nil)
if err != nil {
return nil, err
}
resp, err := s.client.Do(req, nil)
if err != nil {
jerr := NewJiraError(resp, err)
return resp, jerr
}
return resp, nil
}
// GetWorklogs gets all the worklogs for an issue.
// This method is especially important if you need to read all the worklogs, not just the first page.
//
// https://docs.atlassian.com/jira/REST/cloud/#api/2/issue/{issueIdOrKey}/worklog-getIssueWorklog
func (s *IssueService) GetWorklogs(issueID string) (*Worklog, *Response, error) {
func (s *IssueService) GetWorklogs(issueID string, options ...func(*http.Request) error) (*Worklog, *Response, error) {
apiEndpoint := fmt.Sprintf("rest/api/2/issue/%s/worklog", issueID)
req, err := s.client.NewRequest("GET", apiEndpoint, nil)
@ -613,18 +679,41 @@ func (s *IssueService) GetWorklogs(issueID string) (*Worklog, *Response, error)
return nil, nil, err
}
for _, option := range options {
err = option(req)
if err != nil {
return nil, nil, err
}
}
v := new(Worklog)
resp, err := s.client.Do(req, v)
return v, resp, err
}
// Applies query options to http request.
// This helper is meant to be used with all "QueryOptions" structs.
func WithQueryOptions(options interface{}) func(*http.Request) error {
q, err := query.Values(options)
if err != nil {
return func(*http.Request) error {
return err
}
}
return func(r *http.Request) error {
r.URL.RawQuery = q.Encode()
return nil
}
}
// Create creates an issue or a sub-task from a JSON representation.
// Creating a sub-task is similar to creating a regular issue, with two important differences:
// The issueType field must correspond to a sub-task issue type and you must provide a parent field in the issue create request containing the id or key of the parent issue.
//
// JIRA API docs: https://docs.atlassian.com/jira/REST/latest/#api/2/issue-createIssues
func (s *IssueService) Create(issue *Issue) (*Issue, *Response, error) {
apiEndpoint := "rest/api/2/issue/"
apiEndpoint := "rest/api/2/issue"
req, err := s.client.NewRequest("POST", apiEndpoint, issue)
if err != nil {
return nil, nil, err
@ -648,12 +737,17 @@ func (s *IssueService) Create(issue *Issue) (*Issue, *Response, error) {
return responseIssue, resp, nil
}
// Update updates an issue from a JSON representation. The issue is found by key.
// UpdateWithOptions updates an issue from a JSON representation,
// while also specifiying query params. The issue is found by key.
//
// JIRA API docs: https://docs.atlassian.com/jira/REST/cloud/#api/2/issue-editIssue
func (s *IssueService) Update(issue *Issue) (*Issue, *Response, error) {
func (s *IssueService) UpdateWithOptions(issue *Issue, opts *UpdateQueryOptions) (*Issue, *Response, error) {
apiEndpoint := fmt.Sprintf("rest/api/2/issue/%v", issue.Key)
req, err := s.client.NewRequest("PUT", apiEndpoint, issue)
url, err := addOptions(apiEndpoint, opts)
if err != nil {
return nil, nil, err
}
req, err := s.client.NewRequest("PUT", url, issue)
if err != nil {
return nil, nil, err
}
@ -669,6 +763,13 @@ func (s *IssueService) Update(issue *Issue) (*Issue, *Response, error) {
return &ret, resp, nil
}
// Update updates an issue from a JSON representation. The issue is found by key.
//
// JIRA API docs: https://docs.atlassian.com/jira/REST/cloud/#api/2/issue-editIssue
func (s *IssueService) Update(issue *Issue) (*Issue, *Response, error) {
return s.UpdateWithOptions(issue, nil)
}
// UpdateIssue updates an issue from a JSON representation. The issue is found by key.
//
// https://docs.atlassian.com/jira/REST/7.4.0/#api/2/issue-editIssue
@ -718,7 +819,7 @@ func (s *IssueService) UpdateComment(issueID string, comment *Comment) (*Comment
Body: comment.Body,
}
apiEndpoint := fmt.Sprintf("rest/api/2/issue/%s/comment/%s", issueID, comment.ID)
req, err := s.client.NewRequest("POST", apiEndpoint, reqBody)
req, err := s.client.NewRequest("PUT", apiEndpoint, reqBody)
if err != nil {
return nil, nil, err
}
@ -732,16 +833,42 @@ func (s *IssueService) UpdateComment(issueID string, comment *Comment) (*Comment
return responseComment, resp, nil
}
// DeleteComment Deletes a comment from an issueID.
//
// JIRA API docs: https://developer.atlassian.com/cloud/jira/platform/rest/v3/#api-api-3-issue-issueIdOrKey-comment-id-delete
func (s *IssueService) DeleteComment(issueID, commentID string) error {
apiEndpoint := fmt.Sprintf("rest/api/2/issue/%s/comment/%s", issueID, commentID)
req, err := s.client.NewRequest("DELETE", apiEndpoint, nil)
if err != nil {
return err
}
resp, err := s.client.Do(req, nil)
if err != nil {
jerr := NewJiraError(resp, err)
return jerr
}
return nil
}
// AddWorklogRecord adds a new worklog record to issueID.
//
// https://developer.atlassian.com/cloud/jira/platform/rest/#api-api-2-issue-issueIdOrKey-worklog-post
func (s *IssueService) AddWorklogRecord(issueID string, record *WorklogRecord) (*WorklogRecord, *Response, error) {
func (s *IssueService) AddWorklogRecord(issueID string, record *WorklogRecord, options ...func(*http.Request) error) (*WorklogRecord, *Response, error) {
apiEndpoint := fmt.Sprintf("rest/api/2/issue/%s/worklog", issueID)
req, err := s.client.NewRequest("POST", apiEndpoint, record)
if err != nil {
return nil, nil, err
}
for _, option := range options {
err = option(req)
if err != nil {
return nil, nil, err
}
}
responseRecord := new(WorklogRecord)
resp, err := s.client.Do(req, responseRecord)
if err != nil {
@ -778,8 +905,22 @@ func (s *IssueService) Search(jql string, options *SearchOptions) ([]Issue, *Res
if options == nil {
u = fmt.Sprintf("rest/api/2/search?jql=%s", url.QueryEscape(jql))
} else {
u = fmt.Sprintf("rest/api/2/search?jql=%s&startAt=%d&maxResults=%d&expand=%s&fields=%s&validateQuery=%s", url.QueryEscape(jql),
options.StartAt, options.MaxResults, options.Expand, strings.Join(options.Fields, ","), options.ValidateQuery)
u = "rest/api/2/search?jql=" + url.QueryEscape(jql)
if options.StartAt != 0 {
u += fmt.Sprintf("&startAt=%d", options.StartAt)
}
if options.MaxResults != 0 {
u += fmt.Sprintf("&maxResults=%d", options.MaxResults)
}
if options.Expand != "" {
u += fmt.Sprintf("&expand=%s", options.Expand)
}
if strings.Join(options.Fields, ",") != "" {
u += fmt.Sprintf("&fields=%s", strings.Join(options.Fields, ","))
}
if options.ValidateQuery != "" {
u += fmt.Sprintf("&validateQuery=%s", options.ValidateQuery)
}
}
req, err := s.client.NewRequest("GET", u, nil)
@ -1088,3 +1229,32 @@ func (s *IssueService) RemoveWatcher(issueID string, userName string) (*Response
return resp, err
}
// UpdateAssignee updates the user assigned to work on the given issue
//
// JIRA API docs: https://docs.atlassian.com/software/jira/docs/api/REST/7.10.2/#api/2/issue-assign
func (s *IssueService) UpdateAssignee(issueID string, assignee *User) (*Response, error) {
apiEndPoint := fmt.Sprintf("rest/api/2/issue/%s/assignee", issueID)
req, err := s.client.NewRequest("PUT", apiEndPoint, assignee)
if err != nil {
return nil, err
}
resp, err := s.client.Do(req, nil)
if err != nil {
err = NewJiraError(resp, err)
}
return resp, err
}
func (c ChangelogHistory) CreatedTime() (time.Time, error) {
var t time.Time
// Ignore null
if string(c.Created) == "null" {
return t, nil
}
t, err := time.Parse("2006-01-02T15:04:05.999-0700", c.Created)
return t, err
}