Support deleting vote on a revision

Right now deleting a vote is done by:
'DELETE /changes/{change-id}/reviewers/{account-id}/votes/{label-id}'
A new REST endpoint is added as:
'DELETE /changes/{change-id}/revisions/{revision-id}/reviewers/{account-id}/votes/{label-id}'

This new endpoint can help prevent deleting the vote (with same label)
from a newer patch set by mistake.

'List Revision Reviewers' and 'List Revision Votes' endpoints are also
added as by-products. All of the three endpoints are limited on current
revision.

Change-Id: I85fac04db986c2133cd39d2d06648d1dad8c3b4c
This commit is contained in:
Changcheng Xiao
2017-01-02 12:38:30 +01:00
parent 4d03f48c9e
commit 2fcae69087
13 changed files with 551 additions and 2 deletions

View File

@@ -4762,6 +4762,131 @@ describes the resulting cherry picked change.
}
----
[[revision-reviewer-endpoints]]
== Revision Reviewer Endpoints
[[list-revision-reviewers]]
=== List Revision Reviewers
--
'GET /changes/link:#change-id[\{change-id\}]/revisions/link:#revision-id[\{revision-id\}]/reviewers/'
--
Lists the reviewers of a revision.
Please note that only the current revision is supported.
As result a list of link:#reviewer-info[ReviewerInfo] entries is returned.
.Request
----
GET /changes/myProject~master~I8473b95934b5732ac55d26311a706c9c2bde9940/revisions/674ac754f91e64a0efb8087e59a176484bd534d1/reviewers/ HTTP/1.0
----
.Response
----
HTTP/1.1 200 OK
Content-Disposition: attachment
Content-Type: application/json; charset=UTF-8
)]}'
[
{
"approvals": {
"Verified": "+1",
"Code-Review": "+2"
},
"_account_id": 1000096,
"name": "John Doe",
"email": "john.doe@example.com"
},
{
"approvals": {
"Verified": " 0",
"Code-Review": "-1"
},
"_account_id": 1000097,
"name": "Jane Roe",
"email": "jane.roe@example.com"
}
]
----
[[list-revision-votes]]
=== List Revision Votes
--
'GET /changes/link:#change-id[\{change-id\}]/revisions/link:#revision-id[\{revision-id\}]/reviewers/link:rest-api-accounts.html#account-id[\{account-id\}]/votes/'
--
Lists the votes for a specific reviewer of the revision.
Please note that only the current revision is supported.
.Request
----
GET /changes/myProject~master~I8473b95934b5732ac55d26311a706c9c2bde9940/revisions/674ac754f91e64a0efb8087e59a176484bd534d1/reviewers/John%20Doe/votes/ HTTP/1.0
----
As result a map is returned that maps the label name to the label value.
The entries in the map are sorted by label name.
.Response
----
HTTP/1.1 200 OK
Content-Disposition: attachment
Content-Type: application/json;charset=UTF-8
)]}'
{
"Code-Review": -1,
"Verified": 1,
"Work-In-Progress": 1
}
----
[[delete-revision-vote]]
=== Delete Revision Vote
--
'DELETE /changes/link:#change-id[\{change-id\}]/revisions/link:#revision-id[\{revision-id\}]
/reviewers/link:rest-api-accounts.html#account-id[\{account-id\}]/votes/link:#label-id[\{label-id\}]' +
'POST /changes/link:#change-id[\{change-id\}]/revisions/link:#revision-id[\{revision-id\}]
/reviewers/link:rest-api-accounts.html#account-id[\{account-id\}]/votes/link:#label-id[\{label-id\}]/delete'
--
Deletes a single vote from a revision. The deletion will be possible only
if the revision is the current revision. By using this endpoint you can prevent
deleting the vote (with same label) from a newer patch set by mistake.
Note, that even when the last vote of a reviewer is removed the reviewer itself
is still listed on the change.
Options can be provided in the request body as a
link:#delete-vote-input[DeleteVoteInput] entity.
.Request
----
DELETE /changes/myProject~master~I8473b95934b5732ac55d26311a706c9c2bde9940/revisions/674ac754f91e64a0efb8087e59a176484bd534d1/reviewers/John%20Doe/votes/Code-Review HTTP/1.0
POST /changes/myProject~master~I8473b95934b5732ac55d26311a706c9c2bde9940/revisions/674ac754f91e64a0efb8087e59a176484bd534d1/reviewers/John%20Doe/votes/Code-Review/delete HTTP/1.0
----
Please note that some proxies prohibit request bodies for DELETE
requests. In this case, if you want to specify options, use a POST
request:
.Request
----
POST /changes/myProject~master~I8473b95934b5732ac55d26311a706c9c2bde9940/revisions/674ac754f91e64a0efb8087e59a176484bd534d1/reviewers/John%20Doe/votes/Code-Review/delete HTTP/1.0
Content-Type: application/json; charset=UTF-8
{
"notify": "NONE"
}
----
.Response
----
HTTP/1.1 204 No Content
----
[[ids]]
== IDs

View File

@@ -21,6 +21,7 @@ import static com.google.gerrit.acceptance.PushOneCommit.PATCH;
import static com.google.gerrit.acceptance.PushOneCommit.PATCH_FILE_ONLY;
import static com.google.gerrit.acceptance.PushOneCommit.SUBJECT;
import static com.google.gerrit.extensions.client.ListChangesOption.DETAILED_LABELS;
import static com.google.gerrit.extensions.client.ReviewerState.REVIEWER;
import static com.google.gerrit.reviewdb.client.Patch.COMMIT_MSG;
import static com.google.gerrit.reviewdb.client.Patch.MERGE_LIST;
import static java.nio.charset.StandardCharsets.UTF_8;
@@ -30,6 +31,7 @@ import static org.junit.Assert.fail;
import com.google.common.base.Joiner;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Iterables;
import com.google.common.collect.Iterators;
import com.google.gerrit.acceptance.AbstractDaemonTest;
@@ -46,6 +48,7 @@ import com.google.gerrit.extensions.api.changes.RevisionApi;
import com.google.gerrit.extensions.api.projects.BranchInput;
import com.google.gerrit.extensions.client.ChangeStatus;
import com.google.gerrit.extensions.client.SubmitType;
import com.google.gerrit.extensions.common.AccountInfo;
import com.google.gerrit.extensions.common.ApprovalInfo;
import com.google.gerrit.extensions.common.ChangeInfo;
import com.google.gerrit.extensions.common.ChangeMessageInfo;
@@ -58,8 +61,10 @@ import com.google.gerrit.extensions.common.RevisionInfo;
import com.google.gerrit.extensions.restapi.BadRequestException;
import com.google.gerrit.extensions.restapi.BinaryResult;
import com.google.gerrit.extensions.restapi.ETagView;
import com.google.gerrit.extensions.restapi.MethodNotAllowedException;
import com.google.gerrit.extensions.restapi.ResourceConflictException;
import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
import com.google.gerrit.reviewdb.client.Account;
import com.google.gerrit.reviewdb.client.Branch;
import com.google.gerrit.reviewdb.client.Change;
import com.google.gerrit.reviewdb.client.PatchSetApproval;
@@ -1053,6 +1058,71 @@ public class RevisionIT extends AbstractDaemonTest {
oldETag = checkETag(getRevisionActions, r2, oldETag);
}
@Test
public void deleteVoteOnNonCurrentPatchSet() throws Exception {
PushOneCommit.Result r = createChange(); // patch set 1
gApi.changes()
.id(r.getChangeId())
.revision(r.getCommit().name())
.review(ReviewInput.approve());
// patch set 2
amendChange(r.getChangeId());
// code-review
setApiUser(user);
recommend(r.getChangeId());
// check if it's blocked to delete a vote on a non-current patch set.
exception.expect(MethodNotAllowedException.class);
exception.expectMessage("Cannot access on non-current patch set");
setApiUser(admin);
gApi.changes()
.id(r.getChangeId())
.revision(r.getCommit().getName())
.reviewer(user.getId().toString())
.deleteVote("Code-Review");
}
@Test
public void deleteVoteOnCurrentPatchSet() throws Exception {
PushOneCommit.Result r = createChange(); // patch set 1
gApi.changes()
.id(r.getChangeId())
.revision(r.getCommit().name())
.review(ReviewInput.approve());
// patch set 2
amendChange(r.getChangeId());
// code-review
setApiUser(user);
recommend(r.getChangeId());
setApiUser(admin);
gApi.changes()
.id(r.getChangeId())
.current()
.reviewer(user.getId().toString())
.deleteVote("Code-Review");
Map<String, Short> m = gApi.changes()
.id(r.getChangeId())
.current()
.reviewer(user.getId().toString())
.votes();
assertThat(m).containsExactly("Code-Review", Short.valueOf((short)0));
ChangeInfo c = gApi.changes().id(r.getChangeId()).get();
ChangeMessageInfo message = Iterables.getLast(c.messages);
assertThat(message.author._accountId).isEqualTo(admin.getId().get());
assertThat(message.message).isEqualTo(
"Removed Code-Review+1 by User <user@example.com>\n");
assertThat(getReviewers(c.reviewers.get(REVIEWER)))
.containsExactlyElementsIn(ImmutableSet.of(admin.getId(), user.getId()));
}
private PushOneCommit.Result updateChange(PushOneCommit.Result r,
String content) throws Exception {
PushOneCommit push = pushFactory.create(db, admin.getIdent(), testRepo,
@@ -1178,4 +1248,9 @@ public class RevisionIT extends AbstractDaemonTest {
.findFirst()
.get();
}
private static Iterable<Account.Id> getReviewers(
Collection<AccountInfo> r) {
return Iterables.transform(r, a -> new Account.Id(a._accountId));
}
}

View File

@@ -48,6 +48,7 @@ public interface RevisionApi {
ChangeApi rebase(RebaseInput in) throws RestApiException;
boolean canRebase() throws RestApiException;
RevisionReviewerApi reviewer(String id) throws RestApiException;
void setReviewed(String path, boolean reviewed) throws RestApiException;
Set<String> reviewed() throws RestApiException;
@@ -162,6 +163,11 @@ public interface RevisionApi {
throw new NotImplementedException();
}
@Override
public RevisionReviewerApi reviewer(String id) throws RestApiException {
throw new NotImplementedException();
}
@Override
public void setReviewed(String path, boolean reviewed) throws RestApiException {
throw new NotImplementedException();

View File

@@ -0,0 +1,50 @@
// Copyright (C) 2017 The Android Open Source Project
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package com.google.gerrit.extensions.api.changes;
import com.google.gerrit.extensions.restapi.NotImplementedException;
import com.google.gerrit.extensions.restapi.RestApiException;
import java.util.Map;
public interface RevisionReviewerApi {
Map<String, Short> votes() throws RestApiException;
void deleteVote(String label) throws RestApiException;
void deleteVote(DeleteVoteInput input) throws RestApiException;
/**
* A default implementation which allows source compatibility
* when adding new methods to the interface.
**/
class NotImplemented implements RevisionReviewerApi {
@Override
public Map<String, Short> votes() throws RestApiException {
throw new NotImplementedException();
}
@Override
public void deleteVote(String label) throws RestApiException {
throw new NotImplementedException();
}
@Override
public void deleteVote(DeleteVoteInput input) throws RestApiException {
throw new NotImplementedException();
}
}
}

View File

@@ -29,5 +29,6 @@ public class Module extends FactoryModule {
factory(RevisionApiImpl.Factory.class);
factory(FileApiImpl.Factory.class);
factory(ReviewerApiImpl.Factory.class);
factory(RevisionReviewerApiImpl.Factory.class);
}
}

View File

@@ -26,6 +26,7 @@ import com.google.gerrit.extensions.api.changes.FileApi;
import com.google.gerrit.extensions.api.changes.RebaseInput;
import com.google.gerrit.extensions.api.changes.ReviewInput;
import com.google.gerrit.extensions.api.changes.RevisionApi;
import com.google.gerrit.extensions.api.changes.RevisionReviewerApi;
import com.google.gerrit.extensions.api.changes.RobotCommentApi;
import com.google.gerrit.extensions.api.changes.SubmitInput;
import com.google.gerrit.extensions.client.SubmitType;
@@ -63,6 +64,7 @@ import com.google.gerrit.server.change.Rebase;
import com.google.gerrit.server.change.RebaseUtil;
import com.google.gerrit.server.change.Reviewed;
import com.google.gerrit.server.change.RevisionResource;
import com.google.gerrit.server.change.RevisionReviewers;
import com.google.gerrit.server.change.RobotComments;
import com.google.gerrit.server.change.Submit;
import com.google.gerrit.server.change.TestSubmitType;
@@ -89,6 +91,8 @@ class RevisionApiImpl implements RevisionApi {
private final GitRepositoryManager repoManager;
private final Changes changes;
private final RevisionReviewers revisionReviewers;
private final RevisionReviewerApiImpl.Factory revisionReviewerApi;
private final CherryPick cherryPick;
private final DeleteDraftPatchSet deleteDraft;
private final Rebase rebase;
@@ -125,6 +129,8 @@ class RevisionApiImpl implements RevisionApi {
@Inject
RevisionApiImpl(GitRepositoryManager repoManager,
Changes changes,
RevisionReviewers revisionReviewers,
RevisionReviewerApiImpl.Factory revisionReviewerApi,
CherryPick cherryPick,
DeleteDraftPatchSet deleteDraft,
Rebase rebase,
@@ -159,6 +165,8 @@ class RevisionApiImpl implements RevisionApi {
@Assisted RevisionResource r) {
this.repoManager = repoManager;
this.changes = changes;
this.revisionReviewers = revisionReviewers;
this.revisionReviewerApi = revisionReviewerApi;
this.cherryPick = cherryPick;
this.deleteDraft = deleteDraft;
this.rebase = rebase;
@@ -281,6 +289,16 @@ class RevisionApiImpl implements RevisionApi {
}
}
@Override
public RevisionReviewerApi reviewer(String id) throws RestApiException {
try {
return revisionReviewerApi.create(
revisionReviewers.parse(revision, IdString.fromDecoded(id)));
} catch (OrmException e) {
throw new RestApiException("Cannot parse reviewer", e);
}
}
@Override
public void setReviewed(String path, boolean reviewed) throws RestApiException {
try {

View File

@@ -0,0 +1,75 @@
// Copyright (C) 2017 The Android Open Source Project
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package com.google.gerrit.server.api.changes;
import com.google.gerrit.extensions.api.changes.DeleteVoteInput;
import com.google.gerrit.extensions.api.changes.RevisionReviewerApi;
import com.google.gerrit.extensions.restapi.RestApiException;
import com.google.gerrit.server.change.DeleteVote;
import com.google.gerrit.server.change.ReviewerResource;
import com.google.gerrit.server.change.VoteResource;
import com.google.gerrit.server.change.Votes;
import com.google.gerrit.server.git.UpdateException;
import com.google.gwtorm.server.OrmException;
import com.google.inject.Inject;
import com.google.inject.assistedinject.Assisted;
import java.util.Map;
public class RevisionReviewerApiImpl implements RevisionReviewerApi {
interface Factory {
RevisionReviewerApiImpl create(ReviewerResource r);
}
private final ReviewerResource reviewer;
private final Votes.List listVotes;
private final DeleteVote deleteVote;
@Inject
RevisionReviewerApiImpl(Votes.List listVotes,
DeleteVote deleteVote,
@Assisted ReviewerResource reviewer) {
this.listVotes = listVotes;
this.deleteVote = deleteVote;
this.reviewer = reviewer;
}
@Override
public Map<String, Short> votes() throws RestApiException {
try {
return listVotes.apply(reviewer);
} catch (OrmException e) {
throw new RestApiException("Cannot list votes", e);
}
}
@Override
public void deleteVote(String label) throws RestApiException {
try {
deleteVote.apply(new VoteResource(reviewer, label), null);
} catch (UpdateException e) {
throw new RestApiException("Cannot delete vote", e);
}
}
@Override
public void deleteVote(DeleteVoteInput input) throws RestApiException {
try {
deleteVote.apply(new VoteResource(reviewer, input.label), input);
} catch (UpdateException e) {
throw new RestApiException("Cannot delete vote", e);
}
}
}

View File

@@ -22,6 +22,7 @@ import com.google.gerrit.extensions.api.changes.DeleteVoteInput;
import com.google.gerrit.extensions.api.changes.NotifyHandling;
import com.google.gerrit.extensions.restapi.AuthException;
import com.google.gerrit.extensions.restapi.BadRequestException;
import com.google.gerrit.extensions.restapi.MethodNotAllowedException;
import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
import com.google.gerrit.extensions.restapi.Response;
import com.google.gerrit.extensions.restapi.RestApiException;
@@ -108,6 +109,13 @@ public class DeleteVote
}
ReviewerResource r = rsrc.getReviewer();
Change change = r.getChange();
if (r.getRevisionResource() != null
&& !r.getRevisionResource().isCurrent()) {
throw new MethodNotAllowedException(
"Cannot delete vote on non-current patch set");
}
try (BatchUpdate bu = batchUpdateFactory.create(db.get(),
change.getProject(), r.getControl().getUser(), TimeUtil.nowTs())) {
bu.addOp(change.getId(),

View File

@@ -0,0 +1,68 @@
// Copyright (C) 2017 The Android Open Source Project
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package com.google.gerrit.server.change;
import com.google.gerrit.extensions.api.changes.ReviewerInfo;
import com.google.gerrit.extensions.restapi.MethodNotAllowedException;
import com.google.gerrit.extensions.restapi.RestReadView;
import com.google.gerrit.reviewdb.client.Account;
import com.google.gerrit.reviewdb.server.ReviewDb;
import com.google.gerrit.server.ApprovalsUtil;
import com.google.gwtorm.server.OrmException;
import com.google.inject.Inject;
import com.google.inject.Provider;
import com.google.inject.Singleton;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
@Singleton
class ListRevisionReviewers implements RestReadView<RevisionResource> {
private final Provider<ReviewDb> dbProvider;
private final ApprovalsUtil approvalsUtil;
private final ReviewerJson json;
private final ReviewerResource.Factory resourceFactory;
@Inject
ListRevisionReviewers(Provider<ReviewDb> dbProvider,
ApprovalsUtil approvalsUtil,
ReviewerResource.Factory resourceFactory,
ReviewerJson json) {
this.dbProvider = dbProvider;
this.approvalsUtil = approvalsUtil;
this.resourceFactory = resourceFactory;
this.json = json;
}
@Override
public List<ReviewerInfo> apply(RevisionResource rsrc) throws OrmException,
MethodNotAllowedException {
if (!rsrc.isCurrent()) {
throw new MethodNotAllowedException(
"Cannot list reviewers on non-current patch set");
}
Map<Account.Id, ReviewerResource> reviewers = new LinkedHashMap<>();
ReviewDb db = dbProvider.get();
for (Account.Id accountId
: approvalsUtil.getReviewers(db, rsrc.getNotes()).all()) {
if (!reviewers.containsKey(accountId)) {
reviewers.put(accountId, resourceFactory.create(rsrc, accountId));
}
}
return json.format(reviewers.values());
}
}

View File

@@ -36,6 +36,7 @@ public class Module extends RestApiModule {
bind(ChangesCollection.class);
bind(Revisions.class);
bind(Reviewers.class);
bind(RevisionReviewers.class);
bind(DraftComments.class);
bind(Comments.class);
bind(RobotComments.class);
@@ -114,6 +115,8 @@ public class Module extends RestApiModule {
get(REVISION_KIND, "archive").to(GetArchive.class);
get(REVISION_KIND, "mergelist").to(GetMergeList.class);
child(REVISION_KIND, "reviewers").to(RevisionReviewers.class);
child(REVISION_KIND, "drafts").to(DraftComments.class);
put(REVISION_KIND, "drafts").to(CreateDraftComment.class);
get(DRAFT_COMMENT_KIND).to(GetDraftComment.class);

View File

@@ -30,9 +30,11 @@ public class ReviewerResource implements RestResource {
public interface Factory {
ReviewerResource create(ChangeResource change, Account.Id id);
ReviewerResource create(RevisionResource revision, Account.Id id);
}
private final ChangeResource change;
private final RevisionResource revision;
private final IdentifiedUser user;
@AssistedInject
@@ -40,6 +42,16 @@ public class ReviewerResource implements RestResource {
@Assisted ChangeResource change,
@Assisted Account.Id id) {
this.change = change;
this.revision = null;
this.user = userFactory.create(id);
}
@AssistedInject
ReviewerResource(IdentifiedUser.GenericFactory userFactory,
@Assisted RevisionResource revision,
@Assisted Account.Id id) {
this.revision = revision;
this.change = revision.getChangeResource();
this.user = userFactory.create(id);
}
@@ -47,6 +59,10 @@ public class ReviewerResource implements RestResource {
return change;
}
public RevisionResource getRevisionResource() {
return revision;
}
public Change.Id getChangeId() {
return change.getId();
}

View File

@@ -0,0 +1,90 @@
// Copyright (C) 2017 The Android Open Source Project
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package com.google.gerrit.server.change;
import com.google.gerrit.extensions.registration.DynamicMap;
import com.google.gerrit.extensions.restapi.AuthException;
import com.google.gerrit.extensions.restapi.ChildCollection;
import com.google.gerrit.extensions.restapi.IdString;
import com.google.gerrit.extensions.restapi.MethodNotAllowedException;
import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
import com.google.gerrit.extensions.restapi.RestView;
import com.google.gerrit.extensions.restapi.TopLevelResource;
import com.google.gerrit.reviewdb.client.Account;
import com.google.gerrit.reviewdb.server.ReviewDb;
import com.google.gerrit.server.ApprovalsUtil;
import com.google.gerrit.server.account.AccountsCollection;
import com.google.gwtorm.server.OrmException;
import com.google.inject.Inject;
import com.google.inject.Provider;
import com.google.inject.Singleton;
import java.util.Collection;
@Singleton
public class RevisionReviewers implements
ChildCollection<RevisionResource, ReviewerResource> {
private final DynamicMap<RestView<ReviewerResource>> views;
private final Provider<ReviewDb> dbProvider;
private final ApprovalsUtil approvalsUtil;
private final AccountsCollection accounts;
private final ReviewerResource.Factory resourceFactory;
private final ListRevisionReviewers list;
@Inject
RevisionReviewers(Provider<ReviewDb> dbProvider,
ApprovalsUtil approvalsUtil,
AccountsCollection accounts,
ReviewerResource.Factory resourceFactory,
DynamicMap<RestView<ReviewerResource>> views,
ListRevisionReviewers list) {
this.dbProvider = dbProvider;
this.approvalsUtil = approvalsUtil;
this.accounts = accounts;
this.resourceFactory = resourceFactory;
this.views = views;
this.list = list;
}
@Override
public DynamicMap<RestView<ReviewerResource>> views() {
return views;
}
@Override
public RestView<RevisionResource> list() {
return list;
}
@Override
public ReviewerResource parse(RevisionResource rsrc, IdString id)
throws OrmException, ResourceNotFoundException, AuthException,
MethodNotAllowedException {
if (!rsrc.isCurrent()) {
throw new MethodNotAllowedException(
"Cannot access on non-current patch set");
}
Account.Id accountId =
accounts.parse(TopLevelResource.INSTANCE, id).getUser().getAccountId();
Collection<Account.Id> reviewers = approvalsUtil.getReviewers(
dbProvider.get(), rsrc.getNotes()).all();
if (reviewers.contains(accountId)) {
return resourceFactory.create(rsrc, accountId);
}
throw new ResourceNotFoundException(id);
}
}

View File

@@ -18,6 +18,7 @@ import com.google.gerrit.extensions.registration.DynamicMap;
import com.google.gerrit.extensions.restapi.AuthException;
import com.google.gerrit.extensions.restapi.ChildCollection;
import com.google.gerrit.extensions.restapi.IdString;
import com.google.gerrit.extensions.restapi.MethodNotAllowedException;
import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
import com.google.gerrit.extensions.restapi.RestReadView;
import com.google.gerrit.extensions.restapi.RestView;
@@ -56,7 +57,13 @@ public class Votes implements ChildCollection<ReviewerResource, VoteResource> {
@Override
public VoteResource parse(ReviewerResource reviewer, IdString id)
throws ResourceNotFoundException, OrmException, AuthException {
throws ResourceNotFoundException, OrmException, AuthException,
MethodNotAllowedException {
if (!reviewer.getRevisionResource().isCurrent()) {
throw new MethodNotAllowedException(
"Cannot access on non-current patch set");
}
return new VoteResource(reviewer, id.get());
}
@@ -73,7 +80,14 @@ public class Votes implements ChildCollection<ReviewerResource, VoteResource> {
}
@Override
public Map<String, Short> apply(ReviewerResource rsrc) throws OrmException {
public Map<String, Short> apply(ReviewerResource rsrc)
throws OrmException, MethodNotAllowedException {
if (rsrc.getRevisionResource() != null
&& !rsrc.getRevisionResource().isCurrent()) {
throw new MethodNotAllowedException(
"Cannot list votes on non-current patch set");
}
Map<String, Short> votes = new TreeMap<>();
Iterable<PatchSetApproval> byPatchSetUser = approvalsUtil.byPatchSetUser(
db.get(),