Skip to content

Commit 7f227a2

Browse files
authored
Merge pull request #119 from zenixls2/master
add Sprint column for issue. Fix get group member function to return all members.
2 parents 73a371c + 18de7ff commit 7f227a2

6 files changed

Lines changed: 169 additions & 1 deletion

File tree

group.go

Lines changed: 41 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package jira
22

33
import (
44
"fmt"
5+
"net/url"
56
)
67

78
// GroupService handles Groups for the JIRA instance / API.
@@ -48,13 +49,21 @@ type GroupMember struct {
4849
TimeZone string `json:"timeZone,omitempty"`
4950
}
5051

52+
type GroupSearchOptions struct {
53+
StartAt int
54+
MaxResults int
55+
IncludeInactiveUsers bool
56+
}
57+
5158
// Get returns a paginated list of users who are members of the specified group and its subgroups.
5259
// Users in the page are ordered by user names.
5360
// User of this resource is required to have sysadmin or admin permissions.
5461
//
5562
// JIRA API docs: https://docs.atlassian.com/jira/REST/server/#api/2/group-getUsersFromGroup
63+
//
64+
// WARNING: This API only returns the first page of group members
5665
func (s *GroupService) Get(name string) ([]GroupMember, *Response, error) {
57-
apiEndpoint := fmt.Sprintf("/rest/api/2/group/member?groupname=%s", name)
66+
apiEndpoint := fmt.Sprintf("/rest/api/2/group/member?groupname=%s", url.QueryEscape(name))
5867
req, err := s.client.NewRequest("GET", apiEndpoint, nil)
5968
if err != nil {
6069
return nil, nil, err
@@ -69,6 +78,37 @@ func (s *GroupService) Get(name string) ([]GroupMember, *Response, error) {
6978
return group.Members, resp, nil
7079
}
7180

81+
// Get returns a paginated list of members of the specified group and its subgroups.
82+
// Users in the page are ordered by user names.
83+
// User of this resource is required to have sysadmin or admin permissions.
84+
//
85+
// JIRA API docs: https://docs.atlassian.com/jira/REST/server/#api/2/group-getUsersFromGroup
86+
func (s *GroupService) GetWithOptions(name string, options *GroupSearchOptions) ([]GroupMember, *Response, error) {
87+
var apiEndpoint string
88+
if options == nil {
89+
apiEndpoint = fmt.Sprintf("/rest/api/2/group/member?groupname=%s", url.QueryEscape(name))
90+
} else {
91+
apiEndpoint = fmt.Sprintf(
92+
"/rest/api/2/group/member?groupname=%s&startAt=%d&maxResults=%d&includeInactiveUsers=%t",
93+
url.QueryEscape(name),
94+
options.StartAt,
95+
options.MaxResults,
96+
options.IncludeInactiveUsers,
97+
)
98+
}
99+
req, err := s.client.NewRequest("GET", apiEndpoint, nil)
100+
if err != nil {
101+
return nil, nil, err
102+
}
103+
104+
group := new(groupMembersResult)
105+
resp, err := s.client.Do(req, group)
106+
if err != nil {
107+
return nil, resp, err
108+
}
109+
return group.Members, resp, nil
110+
}
111+
72112
// Add adds user to group
73113
//
74114
// JIRA API docs: https://docs.atlassian.com/jira/REST/cloud/#api/2/group-addUserToGroup

group_test.go

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,61 @@ func TestGroupService_Get(t *testing.T) {
2121
}
2222
}
2323

24+
func TestGroupService_GetPage(t *testing.T) {
25+
setup()
26+
defer teardown()
27+
testMux.HandleFunc("/rest/api/2/group/member", func(w http.ResponseWriter, r *http.Request) {
28+
testMethod(t, r, "GET")
29+
testRequestURL(t, r, "/rest/api/2/group/member?groupname=default")
30+
startAt := r.URL.Query().Get("startAt")
31+
if startAt == "0" {
32+
fmt.Fprint(w, `{"self":"http://www.example.com/jira/rest/api/2/group/member?includeInactiveUsers=false&maxResults=2&groupname=default&startAt=0","nextPage":"`+testServer.URL+`/rest/api/2/group/member?groupname=default&includeInactiveUsers=false&maxResults=2&startAt=2","maxResults":2,"startAt":0,"total":4,"isLast":false,"values":[{"self":"http://www.example.com/jira/rest/api/2/user?username=michael","name":"michael","key":"michael","emailAddress":"michael@example.com","displayName":"MichaelScofield","active":true,"timeZone":"Australia/Sydney"},{"self":"http://www.example.com/jira/rest/api/2/user?username=alex","name":"alex","key":"alex","emailAddress":"alex@example.com","displayName":"AlexanderMahone","active":true,"timeZone":"Australia/Sydney"}]}`)
33+
} else if startAt == "2" {
34+
fmt.Fprint(w, `{"self":"http://www.example.com/jira/rest/api/2/group/member?includeInactiveUsers=false&maxResults=2&groupname=default&startAt=2","maxResults":2,"startAt":2,"total":4,"isLast":true,"values":[{"self":"http://www.example.com/jira/rest/api/2/user?username=michael","name":"michael","key":"michael","emailAddress":"michael@example.com","displayName":"MichaelScofield","active":true,"timeZone":"Australia/Sydney"},{"self":"http://www.example.com/jira/rest/api/2/user?username=alex","name":"alex","key":"alex","emailAddress":"alex@example.com","displayName":"AlexanderMahone","active":true,"timeZone":"Australia/Sydney"}]}`)
35+
} else {
36+
t.Errorf("startAt %s", startAt)
37+
}
38+
})
39+
if page, resp, err := testClient.Group.GetWithOptions("default", &GroupSearchOptions{
40+
StartAt: 0,
41+
MaxResults: 2,
42+
IncludeInactiveUsers: false,
43+
}); err != nil {
44+
t.Errorf("Error given: %s %s", err, testServer.URL)
45+
} else if page == nil || len(page) != 2 {
46+
t.Error("Expected members. Group.Members is not 2 or is nil")
47+
} else {
48+
if resp.StartAt != 0 {
49+
t.Errorf("Expect Result StartAt to be 0, but is %d", resp.StartAt)
50+
}
51+
if resp.MaxResults != 2 {
52+
t.Errorf("Expect Result MaxResults to be 2, but is %d", resp.MaxResults)
53+
}
54+
if resp.Total != 4 {
55+
t.Errorf("Expect Result Total to be 4, but is %d", resp.Total)
56+
}
57+
if page, resp, err := testClient.Group.GetWithOptions("default", &GroupSearchOptions{
58+
StartAt: 2,
59+
MaxResults: 2,
60+
IncludeInactiveUsers: false,
61+
}); err != nil {
62+
t.Errorf("Error give: %s %s", err, testServer.URL)
63+
} else if page == nil || len(page) != 2 {
64+
t.Error("Expected members. Group.Members is not 2 or is nil")
65+
} else {
66+
if resp.StartAt != 2 {
67+
t.Errorf("Expect Result StartAt to be 2, but is %d", resp.StartAt)
68+
}
69+
if resp.MaxResults != 2 {
70+
t.Errorf("Expect Result MaxResults to be 2, but is %d", resp.MaxResults)
71+
}
72+
if resp.Total != 4 {
73+
t.Errorf("Expect Result Total to be 4, but is %d", resp.Total)
74+
}
75+
}
76+
}
77+
}
78+
2479
func TestGroupService_Add(t *testing.T) {
2580
setup()
2681
defer teardown()

issue.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,7 @@ type IssueFields struct {
127127
Subtasks []*Subtasks `json:"subtasks,omitempty" structs:"subtasks,omitempty"`
128128
Attachments []*Attachment `json:"attachment,omitempty" structs:"attachment,omitempty"`
129129
Epic *Epic `json:"epic,omitempty" structs:"epic,omitempty"`
130+
Sprint *Sprint `json:"sprint,omitempty" structs:"sprint,omitempty"`
130131
Parent *Parent `json:"parent,omitempty" structs:"parent,omitempty"`
131132
Unknowns tcontainer.MarshalMap
132133
}

jira.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -280,6 +280,10 @@ func (r *Response) populatePageValues(v interface{}) {
280280
r.StartAt = value.StartAt
281281
r.MaxResults = value.MaxResults
282282
r.Total = value.Total
283+
case *groupMembersResult:
284+
r.StartAt = value.StartAt
285+
r.MaxResults = value.MaxResults
286+
r.Total = value.Total
283287
}
284288
return
285289
}

sprint.go

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package jira
22

33
import (
44
"fmt"
5+
"github.com/google/go-querystring/query"
56
)
67

78
// SprintService handles sprints in JIRA Agile API.
@@ -65,3 +66,41 @@ func (s *SprintService) GetIssuesForSprint(sprintID int) ([]Issue, *Response, er
6566

6667
return result.Issues, resp, err
6768
}
69+
70+
// Get returns a full representation of the issue for the given issue key.
71+
// JIRA will attempt to identify the issue by the issueIdOrKey path parameter.
72+
// This can be an issue id, or an issue key.
73+
// If the issue cannot be found via an exact match, JIRA will also look for the issue in a case-insensitive way, or by looking to see if the issue was moved.
74+
//
75+
// The given options will be appended to the query string
76+
//
77+
// JIRA API docs: https://docs.atlassian.com/jira-software/REST/7.3.1/#agile/1.0/issue-getIssue
78+
//
79+
// TODO: create agile service for holding all agile apis' implementation
80+
func (s *SprintService) GetIssue(issueID string, options *GetQueryOptions) (*Issue, *Response, error) {
81+
apiEndpoint := fmt.Sprintf("rest/agile/1.0/issue/%s", issueID)
82+
83+
req, err := s.client.NewRequest("GET", apiEndpoint, nil)
84+
85+
if err != nil {
86+
return nil, nil, err
87+
}
88+
89+
if options != nil {
90+
q, err := query.Values(options)
91+
if err != nil {
92+
return nil, nil, err
93+
}
94+
req.URL.RawQuery = q.Encode()
95+
}
96+
97+
issue := new(Issue)
98+
resp, err := s.client.Do(req, issue)
99+
100+
if err != nil {
101+
jerr := NewJiraError(resp, err)
102+
return nil, resp, jerr
103+
}
104+
105+
return issue, resp, nil
106+
}

sprint_test.go

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import (
55
"fmt"
66
"io/ioutil"
77
"net/http"
8+
"reflect"
89
"testing"
910
)
1011

@@ -65,3 +66,31 @@ func TestSprintService_GetIssuesForSprint(t *testing.T) {
6566
}
6667

6768
}
69+
70+
func TestSprintService_GetIssue(t *testing.T) {
71+
setup()
72+
defer teardown()
73+
74+
testAPIEndpoint := "/rest/agile/1.0/issue/10002"
75+
76+
testMux.HandleFunc(testAPIEndpoint, func(w http.ResponseWriter, r *http.Request) {
77+
testMethod(t, r, "GET")
78+
testRequestURL(t, r, testAPIEndpoint)
79+
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}]},"sprint": {"id": 37,"self": "http://www.example.com/jira/rest/agile/1.0/sprint/13", "state": "future", "name": "sprint 2"}, "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":{}}`)
80+
})
81+
82+
issue, _, err := testClient.Sprint.GetIssue("10002", nil)
83+
if issue == nil {
84+
t.Errorf("Expected issue. Issue is nil %v", err)
85+
}
86+
if !reflect.DeepEqual(issue.Fields.Labels, []string{"test"}) {
87+
t.Error("Expected labels for the returned issue")
88+
}
89+
if len(issue.Fields.Comments.Comments) != 1 {
90+
t.Errorf("Expected one comment, %v found", len(issue.Fields.Comments.Comments))
91+
}
92+
if err != nil {
93+
t.Errorf("Error given: %s", err)
94+
}
95+
96+
}

0 commit comments

Comments
 (0)