Skip to content

Commit 0237c55

Browse files
committed
add support for multiple issue assignees
Fixes #362. Change-Id: I39d142b4ef54bf514140a56b5fcbbcfa784f0c7f
1 parent a8b8751 commit 0237c55

File tree

5 files changed

+119
-3
lines changed

5 files changed

+119
-3
lines changed

github/github.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,9 @@ const (
7575

7676
// https://developer.github.com/changes/2016-04-04-git-signing-api-preview/
7777
mediaTypeGitSigningPreview = "application/vnd.github.cryptographer-preview+json"
78+
79+
// https://developer.github.com/changes/2016-5-27-multiple-assignees/
80+
mediaTypeMultipleAssigneesPreview = "application/vnd.github.cerberus-preview+json"
7881
)
7982

8083
// A Client manages communication with the GitHub API.

github/issues.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ type Issue struct {
3737
PullRequestLinks *PullRequestLinks `json:"pull_request,omitempty"`
3838
Repository *Repository `json:"repository,omitempty"`
3939
Reactions *Reactions `json:"reactions,omitempty"`
40+
Assignees []*User `json:"assignees,omitempty"`
4041

4142
// TextMatches is only populated from search results that request text matches
4243
// See: search.go and https://developer.github.com/v3/search/#text-match-metadata
@@ -57,6 +58,7 @@ type IssueRequest struct {
5758
Assignee *string `json:"assignee,omitempty"`
5859
State *string `json:"state,omitempty"`
5960
Milestone *int `json:"milestone,omitempty"`
61+
Assignees *[]string `json:"assignees,omitempty"`
6062
}
6163

6264
// IssueListOptions specifies the optional parameters to the IssuesService.List
@@ -243,6 +245,9 @@ func (s *IssuesService) Create(owner string, repo string, issue *IssueRequest) (
243245
return nil, nil, err
244246
}
245247

248+
// TODO: remove custom Accept header when this API fully launches.
249+
req.Header.Set("Accept", mediaTypeMultipleAssigneesPreview)
250+
246251
i := new(Issue)
247252
resp, err := s.client.Do(req, i)
248253
if err != nil {
@@ -262,6 +267,9 @@ func (s *IssuesService) Edit(owner string, repo string, number int, issue *Issue
262267
return nil, nil, err
263268
}
264269

270+
// TODO: remove custom Accept header when this API fully launches.
271+
req.Header.Set("Accept", mediaTypeMultipleAssigneesPreview)
272+
265273
i := new(Issue)
266274
resp, err := s.client.Do(req, i)
267275
if err != nil {

github/issues_assignees.go

Lines changed: 44 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ import "fmt"
1111
// which issues may be assigned.
1212
//
1313
// GitHub API docs: http://developer.github.com/v3/issues/assignees/#list-assignees
14-
func (s *IssuesService) ListAssignees(owner string, repo string, opt *ListOptions) ([]User, *Response, error) {
14+
func (s *IssuesService) ListAssignees(owner, repo string, opt *ListOptions) ([]User, *Response, error) {
1515
u := fmt.Sprintf("repos/%v/%v/assignees", owner, repo)
1616
u, err := addOptions(u, opt)
1717
if err != nil {
@@ -34,7 +34,7 @@ func (s *IssuesService) ListAssignees(owner string, repo string, opt *ListOption
3434
// IsAssignee checks if a user is an assignee for the specified repository.
3535
//
3636
// GitHub API docs: http://developer.github.com/v3/issues/assignees/#check-assignee
37-
func (s *IssuesService) IsAssignee(owner string, repo string, user string) (bool, *Response, error) {
37+
func (s *IssuesService) IsAssignee(owner, repo, user string) (bool, *Response, error) {
3838
u := fmt.Sprintf("repos/%v/%v/assignees/%v", owner, repo, user)
3939
req, err := s.client.NewRequest("GET", u, nil)
4040
if err != nil {
@@ -44,3 +44,45 @@ func (s *IssuesService) IsAssignee(owner string, repo string, user string) (bool
4444
assignee, err := parseBoolResponse(err)
4545
return assignee, resp, err
4646
}
47+
48+
// AddAssignees adds the provided GitHub users as assignees to the issue.
49+
//
50+
// GitHub API docs: https://developer.github.com/v3/issues/assignees/#add-assignees-to-an-issue
51+
func (s *IssuesService) AddAssignees(owner, repo string, number int, assignees []string) (*Issue, *Response, error) {
52+
users := &struct {
53+
Assignees []string `json:"assignees,omitempty"`
54+
}{Assignees: assignees}
55+
u := fmt.Sprintf("repos/%v/%v/issues/%v/assignees", owner, repo, number)
56+
req, err := s.client.NewRequest("POST", u, users)
57+
if err != nil {
58+
return nil, nil, err
59+
}
60+
61+
// TODO: remove custom Accept header when this API fully launches.
62+
req.Header.Set("Accept", mediaTypeMultipleAssigneesPreview)
63+
64+
issue := &Issue{}
65+
resp, err := s.client.Do(req, issue)
66+
return issue, resp, err
67+
}
68+
69+
// RemoveAssignees removes the provided GitHub users as assignees from the issue.
70+
//
71+
// GitHub API docs: https://developer.github.com/v3/issues/assignees/#remove-assignees-from-an-issue
72+
func (s *IssuesService) RemoveAssignees(owner, repo string, number int, assignees []string) (*Issue, *Response, error) {
73+
users := &struct {
74+
Assignees []string `json:"assignees,omitempty"`
75+
}{Assignees: assignees}
76+
u := fmt.Sprintf("repos/%v/%v/issues/%v/assignees", owner, repo, number)
77+
req, err := s.client.NewRequest("DELETE", u, users)
78+
if err != nil {
79+
return nil, nil, err
80+
}
81+
82+
// TODO: remove custom Accept header when this API fully launches.
83+
req.Header.Set("Accept", mediaTypeMultipleAssigneesPreview)
84+
85+
issue := &Issue{}
86+
resp, err := s.client.Do(req, issue)
87+
return issue, resp, err
88+
}

github/issues_assignees_test.go

Lines changed: 62 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
package github
77

88
import (
9+
"encoding/json"
910
"fmt"
1011
"net/http"
1112
"reflect"
@@ -25,7 +26,7 @@ func TestIssuesService_ListAssignees(t *testing.T) {
2526
opt := &ListOptions{Page: 2}
2627
assignees, _, err := client.Issues.ListAssignees("o", "r", opt)
2728
if err != nil {
28-
t.Errorf("Issues.List returned error: %v", err)
29+
t.Errorf("Issues.ListAssignees returned error: %v", err)
2930
}
3031

3132
want := []User{{ID: Int(1)}}
@@ -96,3 +97,63 @@ func TestIssuesService_IsAssignee_invalidOwner(t *testing.T) {
9697
_, _, err := client.Issues.IsAssignee("%", "r", "u")
9798
testURLParseError(t, err)
9899
}
100+
101+
func TestIssuesService_AddAssignees(t *testing.T) {
102+
setup()
103+
defer teardown()
104+
105+
mux.HandleFunc("/repos/o/r/issues/1/assignees", func(w http.ResponseWriter, r *http.Request) {
106+
var assignees struct {
107+
Assignees []string `json:"assignees,omitempty"`
108+
}
109+
json.NewDecoder(r.Body).Decode(&assignees)
110+
111+
testMethod(t, r, "POST")
112+
testHeader(t, r, "Accept", mediaTypeMultipleAssigneesPreview)
113+
want := []string{"user1", "user2"}
114+
if !reflect.DeepEqual(assignees.Assignees, want) {
115+
t.Errorf("assignees = %+v, want %+v", assignees, want)
116+
}
117+
fmt.Fprint(w, `{"number":1,"assignees":[{"login":"user1"},{"login":"user2"}]}`)
118+
})
119+
120+
got, _, err := client.Issues.AddAssignees("o", "r", 1, []string{"user1", "user2"})
121+
if err != nil {
122+
t.Errorf("Issues.AddAssignees returned error: %v", err)
123+
}
124+
125+
want := &Issue{Number: Int(1), Assignees: []*User{{Login: String("user1")}, {Login: String("user2")}}}
126+
if !reflect.DeepEqual(got, want) {
127+
t.Errorf("Issues.AddAssignees = %+v, want %+v", got, want)
128+
}
129+
}
130+
131+
func TestIssuesService_RemoveAssignees(t *testing.T) {
132+
setup()
133+
defer teardown()
134+
135+
mux.HandleFunc("/repos/o/r/issues/1/assignees", func(w http.ResponseWriter, r *http.Request) {
136+
var assignees struct {
137+
Assignees []string `json:"assignees,omitempty"`
138+
}
139+
json.NewDecoder(r.Body).Decode(&assignees)
140+
141+
testMethod(t, r, "DELETE")
142+
testHeader(t, r, "Accept", mediaTypeMultipleAssigneesPreview)
143+
want := []string{"user1", "user2"}
144+
if !reflect.DeepEqual(assignees.Assignees, want) {
145+
t.Errorf("assignees = %+v, want %+v", assignees, want)
146+
}
147+
fmt.Fprint(w, `{"number":1,"assignees":[]}`)
148+
})
149+
150+
got, _, err := client.Issues.RemoveAssignees("o", "r", 1, []string{"user1", "user2"})
151+
if err != nil {
152+
t.Errorf("Issues.RemoveAssignees returned error: %v", err)
153+
}
154+
155+
want := &Issue{Number: Int(1), Assignees: []*User{}}
156+
if !reflect.DeepEqual(got, want) {
157+
t.Errorf("Issues.RemoveAssignees = %+v, want %+v", got, want)
158+
}
159+
}

github/issues_test.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -189,6 +189,7 @@ func TestIssuesService_Create(t *testing.T) {
189189
json.NewDecoder(r.Body).Decode(v)
190190

191191
testMethod(t, r, "POST")
192+
testHeader(t, r, "Accept", mediaTypeMultipleAssigneesPreview)
192193
if !reflect.DeepEqual(v, input) {
193194
t.Errorf("Request body = %+v, want %+v", v, input)
194195
}
@@ -223,6 +224,7 @@ func TestIssuesService_Edit(t *testing.T) {
223224
json.NewDecoder(r.Body).Decode(v)
224225

225226
testMethod(t, r, "PATCH")
227+
testHeader(t, r, "Accept", mediaTypeMultipleAssigneesPreview)
226228
if !reflect.DeepEqual(v, input) {
227229
t.Errorf("Request body = %+v, want %+v", v, input)
228230
}

0 commit comments

Comments
 (0)