Skip to content

Commit b985037

Browse files
a1012112796lafrikssilverwindzeripath
authored
Add review request api (#11355)
* Add review request api * add : POST /repos/{owner}/{repo}/pulls/{index}/requested_reviewers * Remove : DELET /repos/{owner}/{repo}/pulls/{index}/requested_reviewers * fix some request review bug * block delet request review by models/DeleteReview() Signed-off-by: a1012112796 <[email protected]> * make fmt * fix bug * fix test code * fix typo * Apply suggestion from code review @jonasfranz * fix swagger ref * fix typo Co-authored-by: Lauris BH <[email protected]> * fix comment * Change response message * chang response so some simplfy * Add ErrIllLegalReviewRequest fix some nits * make fmt * Apply suggestions from code review Co-authored-by: silverwind <[email protected]> * * Add team support * fix test * fix an known bug * fix nit * fix test * Apply suggestions from code review Co-authored-by: zeripath <[email protected]> * update get api and add test Co-authored-by: Lauris BH <[email protected]> Co-authored-by: silverwind <[email protected]> Co-authored-by: zeripath <[email protected]>
1 parent b50448b commit b985037

File tree

21 files changed

+694
-171
lines changed

21 files changed

+694
-171
lines changed

integrations/api_issue_test.go

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -153,15 +153,15 @@ func TestAPISearchIssues(t *testing.T) {
153153
var apiIssues []*api.Issue
154154
DecodeJSON(t, resp, &apiIssues)
155155

156-
assert.Len(t, apiIssues, 9)
156+
assert.Len(t, apiIssues, 10)
157157

158158
query := url.Values{}
159159
query.Add("token", token)
160160
link.RawQuery = query.Encode()
161161
req = NewRequest(t, "GET", link.String())
162162
resp = session.MakeRequest(t, req, http.StatusOK)
163163
DecodeJSON(t, resp, &apiIssues)
164-
assert.Len(t, apiIssues, 9)
164+
assert.Len(t, apiIssues, 10)
165165

166166
query.Add("state", "closed")
167167
link.RawQuery = query.Encode()
@@ -182,7 +182,7 @@ func TestAPISearchIssues(t *testing.T) {
182182
req = NewRequest(t, "GET", link.String())
183183
resp = session.MakeRequest(t, req, http.StatusOK)
184184
DecodeJSON(t, resp, &apiIssues)
185-
assert.Len(t, apiIssues, 1)
185+
assert.Len(t, apiIssues, 2)
186186
}
187187

188188
func TestAPISearchIssuesWithLabels(t *testing.T) {
@@ -197,15 +197,15 @@ func TestAPISearchIssuesWithLabels(t *testing.T) {
197197
var apiIssues []*api.Issue
198198
DecodeJSON(t, resp, &apiIssues)
199199

200-
assert.Len(t, apiIssues, 9)
200+
assert.Len(t, apiIssues, 10)
201201

202202
query := url.Values{}
203203
query.Add("token", token)
204204
link.RawQuery = query.Encode()
205205
req = NewRequest(t, "GET", link.String())
206206
resp = session.MakeRequest(t, req, http.StatusOK)
207207
DecodeJSON(t, resp, &apiIssues)
208-
assert.Len(t, apiIssues, 9)
208+
assert.Len(t, apiIssues, 10)
209209

210210
query.Add("labels", "label1")
211211
link.RawQuery = query.Encode()

integrations/api_pull_review_test.go

Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -122,4 +122,110 @@ func TestAPIPullReview(t *testing.T) {
122122
assert.EqualValues(t, 0, review.CodeCommentsCount)
123123
req = NewRequestf(t, http.MethodDelete, "/api/v1/repos/%s/%s/pulls/%d/reviews/%d?token=%s", repo.OwnerName, repo.Name, pullIssue.Index, review.ID, token)
124124
resp = session.MakeRequest(t, req, http.StatusNoContent)
125+
126+
// test get review requests
127+
// to make it simple, use same api with get review
128+
pullIssue12 := models.AssertExistsAndLoadBean(t, &models.Issue{ID: 12}).(*models.Issue)
129+
assert.NoError(t, pullIssue12.LoadAttributes())
130+
repo3 := models.AssertExistsAndLoadBean(t, &models.Repository{ID: pullIssue12.RepoID}).(*models.Repository)
131+
132+
req = NewRequestf(t, http.MethodGet, "/api/v1/repos/%s/%s/pulls/%d/reviews?token=%s", repo3.OwnerName, repo3.Name, pullIssue12.Index, token)
133+
resp = session.MakeRequest(t, req, http.StatusOK)
134+
DecodeJSON(t, resp, &reviews)
135+
assert.EqualValues(t, 11, reviews[0].ID)
136+
assert.EqualValues(t, "REQUEST_REVIEW", reviews[0].State)
137+
assert.EqualValues(t, 0, reviews[0].CodeCommentsCount)
138+
assert.EqualValues(t, false, reviews[0].Stale)
139+
assert.EqualValues(t, true, reviews[0].Official)
140+
assert.EqualValues(t, "test_team", reviews[0].ReviewerTeam.Name)
141+
142+
assert.EqualValues(t, 12, reviews[1].ID)
143+
assert.EqualValues(t, "REQUEST_REVIEW", reviews[1].State)
144+
assert.EqualValues(t, 0, reviews[0].CodeCommentsCount)
145+
assert.EqualValues(t, false, reviews[1].Stale)
146+
assert.EqualValues(t, true, reviews[1].Official)
147+
assert.EqualValues(t, 1, reviews[1].Reviewer.ID)
148+
}
149+
150+
func TestAPIPullReviewRequest(t *testing.T) {
151+
defer prepareTestEnv(t)()
152+
pullIssue := models.AssertExistsAndLoadBean(t, &models.Issue{ID: 3}).(*models.Issue)
153+
assert.NoError(t, pullIssue.LoadAttributes())
154+
repo := models.AssertExistsAndLoadBean(t, &models.Repository{ID: pullIssue.RepoID}).(*models.Repository)
155+
156+
// Test add Review Request
157+
session := loginUser(t, "user2")
158+
token := getTokenForLoggedInUser(t, session)
159+
req := NewRequestWithJSON(t, http.MethodPost, fmt.Sprintf("/api/v1/repos/%s/%s/pulls/%d/requested_reviewers?token=%s", repo.OwnerName, repo.Name, pullIssue.Index, token), &api.PullReviewRequestOptions{
160+
Reviewers: []string{"[email protected]", "user8"},
161+
})
162+
session.MakeRequest(t, req, http.StatusCreated)
163+
164+
// poster of pr can't be reviewer
165+
req = NewRequestWithJSON(t, http.MethodPost, fmt.Sprintf("/api/v1/repos/%s/%s/pulls/%d/requested_reviewers?token=%s", repo.OwnerName, repo.Name, pullIssue.Index, token), &api.PullReviewRequestOptions{
166+
Reviewers: []string{"user1"},
167+
})
168+
session.MakeRequest(t, req, http.StatusUnprocessableEntity)
169+
170+
// test user not exist
171+
req = NewRequestWithJSON(t, http.MethodPost, fmt.Sprintf("/api/v1/repos/%s/%s/pulls/%d/requested_reviewers?token=%s", repo.OwnerName, repo.Name, pullIssue.Index, token), &api.PullReviewRequestOptions{
172+
Reviewers: []string{"testOther"},
173+
})
174+
session.MakeRequest(t, req, http.StatusNotFound)
175+
176+
// Test Remove Review Request
177+
session2 := loginUser(t, "user4")
178+
token2 := getTokenForLoggedInUser(t, session2)
179+
180+
req = NewRequestWithJSON(t, http.MethodDelete, fmt.Sprintf("/api/v1/repos/%s/%s/pulls/%d/requested_reviewers?token=%s", repo.OwnerName, repo.Name, pullIssue.Index, token2), &api.PullReviewRequestOptions{
181+
Reviewers: []string{"user4"},
182+
})
183+
session.MakeRequest(t, req, http.StatusNoContent)
184+
185+
// doer is not admin
186+
req = NewRequestWithJSON(t, http.MethodDelete, fmt.Sprintf("/api/v1/repos/%s/%s/pulls/%d/requested_reviewers?token=%s", repo.OwnerName, repo.Name, pullIssue.Index, token2), &api.PullReviewRequestOptions{
187+
Reviewers: []string{"user8"},
188+
})
189+
session.MakeRequest(t, req, http.StatusUnprocessableEntity)
190+
191+
req = NewRequestWithJSON(t, http.MethodDelete, fmt.Sprintf("/api/v1/repos/%s/%s/pulls/%d/requested_reviewers?token=%s", repo.OwnerName, repo.Name, pullIssue.Index, token), &api.PullReviewRequestOptions{
192+
Reviewers: []string{"user8"},
193+
})
194+
session.MakeRequest(t, req, http.StatusNoContent)
195+
196+
// Test team review request
197+
pullIssue12 := models.AssertExistsAndLoadBean(t, &models.Issue{ID: 12}).(*models.Issue)
198+
assert.NoError(t, pullIssue12.LoadAttributes())
199+
repo3 := models.AssertExistsAndLoadBean(t, &models.Repository{ID: pullIssue12.RepoID}).(*models.Repository)
200+
201+
// Test add Team Review Request
202+
req = NewRequestWithJSON(t, http.MethodPost, fmt.Sprintf("/api/v1/repos/%s/%s/pulls/%d/requested_reviewers?token=%s", repo3.OwnerName, repo3.Name, pullIssue12.Index, token), &api.PullReviewRequestOptions{
203+
TeamReviewers: []string{"team1", "owners"},
204+
})
205+
session.MakeRequest(t, req, http.StatusCreated)
206+
207+
// Test add Team Review Request to not allowned
208+
req = NewRequestWithJSON(t, http.MethodPost, fmt.Sprintf("/api/v1/repos/%s/%s/pulls/%d/requested_reviewers?token=%s", repo3.OwnerName, repo3.Name, pullIssue12.Index, token), &api.PullReviewRequestOptions{
209+
TeamReviewers: []string{"test_team"},
210+
})
211+
session.MakeRequest(t, req, http.StatusUnprocessableEntity)
212+
213+
// Test add Team Review Request to not exist
214+
req = NewRequestWithJSON(t, http.MethodPost, fmt.Sprintf("/api/v1/repos/%s/%s/pulls/%d/requested_reviewers?token=%s", repo3.OwnerName, repo3.Name, pullIssue12.Index, token), &api.PullReviewRequestOptions{
215+
TeamReviewers: []string{"not_exist_team"},
216+
})
217+
session.MakeRequest(t, req, http.StatusNotFound)
218+
219+
// Test Remove team Review Request
220+
req = NewRequestWithJSON(t, http.MethodDelete, fmt.Sprintf("/api/v1/repos/%s/%s/pulls/%d/requested_reviewers?token=%s", repo3.OwnerName, repo3.Name, pullIssue12.Index, token), &api.PullReviewRequestOptions{
221+
TeamReviewers: []string{"team1"},
222+
})
223+
session.MakeRequest(t, req, http.StatusNoContent)
224+
225+
// empty request test
226+
req = NewRequestWithJSON(t, http.MethodPost, fmt.Sprintf("/api/v1/repos/%s/%s/pulls/%d/requested_reviewers?token=%s", repo3.OwnerName, repo3.Name, pullIssue12.Index, token), &api.PullReviewRequestOptions{})
227+
session.MakeRequest(t, req, http.StatusCreated)
228+
229+
req = NewRequestWithJSON(t, http.MethodDelete, fmt.Sprintf("/api/v1/repos/%s/%s/pulls/%d/requested_reviewers?token=%s", repo3.OwnerName, repo3.Name, pullIssue12.Index, token), &api.PullReviewRequestOptions{})
230+
session.MakeRequest(t, req, http.StatusNoContent)
125231
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
d22b4d4daa5be07329fcef6ed458f00cf3392da0

models/error.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2003,7 +2003,7 @@ type ErrNotValidReviewRequest struct {
20032003

20042004
// IsErrNotValidReviewRequest checks if an error is a ErrNotValidReviewRequest.
20052005
func IsErrNotValidReviewRequest(err error) bool {
2006-
_, ok := err.(ErrReviewNotExist)
2006+
_, ok := err.(ErrNotValidReviewRequest)
20072007
return ok
20082008
}
20092009

models/fixtures/issue.yml

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -135,3 +135,15 @@
135135
is_pull: true
136136
created_unix: 1579194806
137137
updated_unix: 1579194806
138+
139+
-
140+
id: 12
141+
repo_id: 3
142+
index: 2
143+
poster_id: 2
144+
name: pull6
145+
content: content for the a pull request
146+
is_closed: false
147+
is_pull: true
148+
created_unix: 1602935696
149+
updated_unix: 1602935696

models/fixtures/pull_request.yml

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,3 +63,16 @@
6363
base_branch: branch1
6464
merge_base: 1234567890abcdef
6565
has_merged: false
66+
67+
-
68+
id: 6
69+
type: 0 # gitea pull request
70+
status: 2 # mergable
71+
issue_id: 12
72+
index: 2
73+
head_repo_id: 3
74+
base_repo_id: 3
75+
head_branch: test_branch
76+
base_branch: master
77+
merge_base: 2a47ca4b614a9f5a
78+
has_merged: false

models/fixtures/repository.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@
4141
is_private: true
4242
num_issues: 1
4343
num_closed_issues: 0
44-
num_pulls: 0
44+
num_pulls: 1
4545
num_closed_pulls: 0
4646
num_watches: 0
4747
num_projects: 1

models/fixtures/review.yml

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,3 +86,22 @@
8686
official: true
8787
updated_unix: 946684815
8888
created_unix: 946684815
89+
90+
-
91+
id: 11
92+
type: 4
93+
reviewer_id: 0
94+
reviewer_team_id: 7
95+
issue_id: 12
96+
official: true
97+
updated_unix: 1602936509
98+
created_unix: 1602936509
99+
100+
-
101+
id: 12
102+
type: 4
103+
reviewer_id: 1
104+
issue_id: 12
105+
official: true
106+
updated_unix: 1603196749
107+
created_unix: 1603196749

models/review.go

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -627,13 +627,14 @@ func AddReviewRequest(issue *Issue, reviewer, doer *User) (*Comment, error) {
627627
}
628628
}
629629

630-
if _, err = createReview(sess, CreateReviewOptions{
630+
review, err = createReview(sess, CreateReviewOptions{
631631
Type: ReviewTypeRequest,
632632
Issue: issue,
633633
Reviewer: reviewer,
634634
Official: official,
635635
Stale: false,
636-
}); err != nil {
636+
})
637+
if err != nil {
637638
return nil, err
638639
}
639640

@@ -644,6 +645,7 @@ func AddReviewRequest(issue *Issue, reviewer, doer *User) (*Comment, error) {
644645
Issue: issue,
645646
RemovedAssignee: false, // Use RemovedAssignee as !isRequest
646647
AssigneeID: reviewer.ID, // Use AssigneeID as reviewer ID
648+
ReviewID: review.ID,
647649
})
648650
if err != nil {
649651
return nil, err
@@ -732,7 +734,7 @@ func AddTeamReviewRequest(issue *Issue, reviewer *Team, doer *User) (*Comment, e
732734
}
733735
}
734736

735-
if _, err = createReview(sess, CreateReviewOptions{
737+
if review, err = createReview(sess, CreateReviewOptions{
736738
Type: ReviewTypeRequest,
737739
Issue: issue,
738740
ReviewerTeam: reviewer,
@@ -755,6 +757,7 @@ func AddTeamReviewRequest(issue *Issue, reviewer *Team, doer *User) (*Comment, e
755757
Issue: issue,
756758
RemovedAssignee: false, // Use RemovedAssignee as !isRequest
757759
AssigneeTeamID: reviewer.ID, // Use AssigneeTeamID as reviewer team ID
760+
ReviewID: review.ID,
758761
})
759762
if err != nil {
760763
return nil, fmt.Errorf("createComment(): %v", err)
@@ -894,6 +897,10 @@ func DeleteReview(r *Review) error {
894897
return fmt.Errorf("review is not allowed to be 0")
895898
}
896899

900+
if r.Type == ReviewTypeRequest {
901+
return fmt.Errorf("review request can not be deleted using this method")
902+
}
903+
897904
opts := FindCommentsOptions{
898905
Type: CommentTypeCode,
899906
IssueID: r.IssueID,

modules/convert/convert.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -284,6 +284,10 @@ func ToOrganization(org *models.User) *api.Organization {
284284

285285
// ToTeam convert models.Team to api.Team
286286
func ToTeam(team *models.Team) *api.Team {
287+
if team == nil {
288+
return nil
289+
}
290+
287291
return &api.Team{
288292
ID: team.ID,
289293
Name: team.Name,

modules/convert/pull_review.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ func ToPullReview(r *models.Review, doer *models.User) (*api.PullReview, error)
2828
result := &api.PullReview{
2929
ID: r.ID,
3030
Reviewer: ToUser(r.Reviewer, doer != nil, auth),
31+
ReviewerTeam: ToTeam(r.ReviewerTeam),
3132
State: api.ReviewStateUnknown,
3233
Body: r.Content,
3334
CommitID: r.CommitID,

modules/structs/pull_review.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ const (
3030
type PullReview struct {
3131
ID int64 `json:"id"`
3232
Reviewer *User `json:"user"`
33+
ReviewerTeam *Team `json:"team"`
3334
State ReviewStateType `json:"state"`
3435
Body string `json:"body"`
3536
CommitID string `json:"commit_id"`
@@ -90,3 +91,9 @@ type SubmitPullReviewOptions struct {
9091
Event ReviewStateType `json:"event"`
9192
Body string `json:"body"`
9293
}
94+
95+
// PullReviewRequestOptions are options to add or remove pull review requests
96+
type PullReviewRequestOptions struct {
97+
Reviewers []string `json:"reviewers"`
98+
TeamReviewers []string `json:"team_reviewers"`
99+
}

routers/api/v1/api.go

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -827,7 +827,9 @@ func RegisterRoutes(m *macaron.Macaron) {
827827
Get(repo.GetPullReviewComments)
828828
})
829829
})
830-
830+
m.Combo("/requested_reviewers").
831+
Delete(reqToken(), bind(api.PullReviewRequestOptions{}), repo.DeleteReviewRequests).
832+
Post(reqToken(), bind(api.PullReviewRequestOptions{}), repo.CreateReviewRequests)
831833
})
832834
}, mustAllowPulls, reqRepoReader(models.UnitTypeCode), context.ReferencesGitRepo(false))
833835
m.Group("/statuses", func() {

0 commit comments

Comments
 (0)