Merge "Allow to remove specific scores while leaving the reviewer listed"
This commit is contained in:
commit
821754e71a
@ -2264,6 +2264,55 @@ Deletes a reviewer from a change.
|
||||
HTTP/1.1 204 No Content
|
||||
----
|
||||
|
||||
[[list-votes]]
|
||||
=== List Votes
|
||||
--
|
||||
'GET /changes/link:#change-id[\{change-id\}]/reviewers/link:rest-api-accounts.html#account-id[\{account-id\}]/votes/'
|
||||
--
|
||||
|
||||
Lists the votes for a specific reviewer of the change.
|
||||
|
||||
.Request
|
||||
----
|
||||
GET /changes/myProject~master~I8473b95934b5732ac55d26311a706c9c2bde9940/revisions/674ac754f91e64a0efb8087e59a176484bd534d1/files/ 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-vote]]
|
||||
=== Delete Vote
|
||||
--
|
||||
'DELETE /changes/link:#change-id[\{change-id\}]/reviewers/link:rest-api-accounts.html#account-id[\{account-id\}]/votes/link:#label-id[\{label-id\}]'
|
||||
--
|
||||
|
||||
Deletes a single vote from a change. Note, that even when the last vote of
|
||||
a reviewer is removed the reviewer itself is still listed on the change.
|
||||
|
||||
.Request
|
||||
----
|
||||
DELETE /changes/myProject~master~I8473b95934b5732ac55d26311a706c9c2bde9940/reviewers/John%20Doe/votes/Code-Review HTTP/1.0
|
||||
----
|
||||
|
||||
.Response
|
||||
----
|
||||
HTTP/1.1 204 No Content
|
||||
----
|
||||
|
||||
[[revision-endpoints]]
|
||||
== Revision Endpoints
|
||||
|
||||
@ -3747,6 +3796,10 @@ UUID of a published comment.
|
||||
=== \{draft-id\}
|
||||
UUID of a draft comment.
|
||||
|
||||
[[label-id]]
|
||||
=== \{label-id\}
|
||||
The name of the label.
|
||||
|
||||
[[file-id]]
|
||||
\{file-id\}
|
||||
~~~~~~~~~~~~
|
||||
|
@ -26,6 +26,8 @@ import static com.google.gerrit.server.project.Util.category;
|
||||
import static com.google.gerrit.server.project.Util.value;
|
||||
import static com.google.gerrit.testutil.GerritServerTests.isNoteDbTestEnabled;
|
||||
|
||||
import com.google.common.base.Function;
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
import com.google.common.collect.Iterables;
|
||||
import com.google.gerrit.acceptance.AbstractDaemonTest;
|
||||
import com.google.gerrit.acceptance.NoHttpd;
|
||||
@ -46,7 +48,9 @@ import com.google.gerrit.extensions.common.ChangeMessageInfo;
|
||||
import com.google.gerrit.extensions.common.GitPerson;
|
||||
import com.google.gerrit.extensions.common.LabelInfo;
|
||||
import com.google.gerrit.extensions.common.RevisionInfo;
|
||||
import com.google.gerrit.extensions.restapi.AuthException;
|
||||
import com.google.gerrit.extensions.restapi.ResourceConflictException;
|
||||
import com.google.gerrit.reviewdb.client.Account;
|
||||
import com.google.gerrit.reviewdb.client.AccountGroup;
|
||||
import com.google.gerrit.reviewdb.client.Change;
|
||||
import com.google.gerrit.reviewdb.client.PatchSet;
|
||||
@ -63,6 +67,7 @@ import java.util.Collection;
|
||||
import java.util.EnumSet;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
@NoHttpd
|
||||
public class ChangeIT extends AbstractDaemonTest {
|
||||
@ -378,6 +383,115 @@ public class ChangeIT extends AbstractDaemonTest {
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void listVotes() throws Exception {
|
||||
PushOneCommit.Result r = createChange();
|
||||
gApi.changes()
|
||||
.id(r.getChangeId())
|
||||
.revision(r.getCommit().name())
|
||||
.review(ReviewInput.approve());
|
||||
|
||||
Map<String, Short> m = gApi.changes()
|
||||
.id(r.getChangeId())
|
||||
.reviewer(admin.getId().toString())
|
||||
.votes();
|
||||
|
||||
assertThat(m).hasSize(1);
|
||||
assertThat(m).containsEntry("Code-Review", new Short((short)2));
|
||||
|
||||
setApiUser(user);
|
||||
gApi.changes()
|
||||
.id(r.getChangeId())
|
||||
.revision(r.getCommit().name())
|
||||
.review(ReviewInput.dislike());
|
||||
|
||||
m = gApi.changes()
|
||||
.id(r.getChangeId())
|
||||
.reviewer(user.getId().toString())
|
||||
.votes();
|
||||
|
||||
assertThat(m).hasSize(1);
|
||||
assertThat(m).containsEntry("Code-Review", new Short((short)-1));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void deleteVote() throws Exception {
|
||||
PushOneCommit.Result r = createChange();
|
||||
gApi.changes()
|
||||
.id(r.getChangeId())
|
||||
.revision(r.getCommit().name())
|
||||
.review(ReviewInput.approve());
|
||||
|
||||
setApiUser(user);
|
||||
gApi.changes()
|
||||
.id(r.getChangeId())
|
||||
.revision(r.getCommit().name())
|
||||
.review(ReviewInput.recommend());
|
||||
|
||||
setApiUser(admin);
|
||||
gApi.changes()
|
||||
.id(r.getChangeId())
|
||||
.reviewer(admin.getId().toString())
|
||||
.deleteVote("Code-Review");
|
||||
|
||||
Map<String, Short> m = gApi.changes()
|
||||
.id(r.getChangeId())
|
||||
.reviewer(admin.getId().toString())
|
||||
.votes();
|
||||
|
||||
if (isNoteDbTestEnabled()) {
|
||||
// When notedb is enabled each reviewer is explicitly recorded in the
|
||||
// notedb and this record stays even when all votes of that user have been
|
||||
// deleted, hence there is no dummy 0 approval left when a vote is
|
||||
// deleted.
|
||||
assertThat(m).isEmpty();
|
||||
} else {
|
||||
// When notedb is disabled there is a dummy 0 approval on the change so
|
||||
// that the user is still returned as CC when all votes of that user have
|
||||
// been deleted.
|
||||
assertThat(m).containsEntry("Code-Review", new Short((short)0));
|
||||
}
|
||||
|
||||
ChangeInfo c = gApi.changes()
|
||||
.id(r.getChangeId())
|
||||
.get();
|
||||
|
||||
assertThat(Iterables.getLast(c.messages).message).isEqualTo(
|
||||
"Removed Code-Review+2 by Administrator <admin@example.com>\n");
|
||||
if (isNoteDbTestEnabled()) {
|
||||
// When notedb is enabled each reviewer is explicitly recorded in the
|
||||
// notedb and this record stays even when all votes of that user have been
|
||||
// deleted.
|
||||
assertThat(getReviewers(c.reviewers.get(REVIEWER)))
|
||||
.containsExactlyElementsIn(
|
||||
ImmutableSet.of(admin.getId(), user.getId()));
|
||||
} else {
|
||||
// When notedb is disabled users that have only dummy 0 approvals on the
|
||||
// change are returned as CC and not as REVIEWER.
|
||||
assertThat(getReviewers(c.reviewers.get(REVIEWER)))
|
||||
.containsExactlyElementsIn(ImmutableSet.of(user.getId()));
|
||||
assertThat(getReviewers(c.reviewers.get(CC)))
|
||||
.containsExactlyElementsIn(ImmutableSet.of(admin.getId()));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void deleteVoteNotPermitted() throws Exception {
|
||||
PushOneCommit.Result r = createChange();
|
||||
gApi.changes()
|
||||
.id(r.getChangeId())
|
||||
.revision(r.getCommit().name())
|
||||
.review(ReviewInput.approve());
|
||||
|
||||
setApiUser(user);
|
||||
exception.expect(AuthException.class);
|
||||
exception.expectMessage("delete not permitted");
|
||||
gApi.changes()
|
||||
.id(r.getChangeId())
|
||||
.reviewer(admin.getId().toString())
|
||||
.deleteVote("Code-Review");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void createEmptyChange() throws Exception {
|
||||
ChangeInfo in = new ChangeInfo();
|
||||
@ -677,4 +791,15 @@ public class ChangeIT extends AbstractDaemonTest {
|
||||
assertThat(rev2.pushCertificate.certificate).isNull();
|
||||
assertThat(rev2.pushCertificate.key).isNull();
|
||||
}
|
||||
|
||||
|
||||
private static Iterable<Account.Id> getReviewers(
|
||||
Collection<AccountInfo> r) {
|
||||
return Iterables.transform(r, new Function<AccountInfo, Account.Id>() {
|
||||
@Override
|
||||
public Account.Id apply(AccountInfo account) {
|
||||
return new Account.Id(account._accountId);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -58,6 +58,19 @@ public interface ChangeApi {
|
||||
*/
|
||||
RevisionApi revision(String id) throws RestApiException;
|
||||
|
||||
/**
|
||||
* Look up the reviewer of the change.
|
||||
* <p>
|
||||
* @param id ID of the account, can be a string of the format
|
||||
* "Full Name <mail@example.com>", just the email address, a full name
|
||||
* if it is unique, an account ID, a user name or 'self' for the
|
||||
* calling user.
|
||||
* @return API for accessing the reviewer.
|
||||
* @throws RestApiException if id is not account ID or is a user that isn't
|
||||
* known to be a reviewer for this change.
|
||||
*/
|
||||
ReviewerApi reviewer(String id) throws RestApiException;
|
||||
|
||||
void abandon() throws RestApiException;
|
||||
void abandon(AbandonInput in) throws RestApiException;
|
||||
|
||||
@ -176,6 +189,11 @@ public interface ChangeApi {
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ReviewerApi reviewer(String id) throws RestApiException {
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public RevisionApi revision(String id) throws RestApiException {
|
||||
throw new NotImplementedException();
|
||||
|
@ -0,0 +1,25 @@
|
||||
// Copyright (C) 2014 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.RestApiException;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
public interface ReviewerApi {
|
||||
|
||||
Map<String, Short> votes() throws RestApiException;
|
||||
void deleteVote(String label) throws RestApiException;
|
||||
}
|
@ -31,6 +31,7 @@ import java.sql.Timestamp;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
import java.util.SortedSet;
|
||||
import java.util.TreeSet;
|
||||
@ -84,6 +85,16 @@ public class ChangeInfo extends JavaScriptObject {
|
||||
return allLabels().keySet();
|
||||
}
|
||||
|
||||
public final Set<Integer> removableReviewerIds() {
|
||||
Set<Integer> removable = new HashSet<>();
|
||||
if (removableReviewers() != null) {
|
||||
for (AccountInfo a : Natives.asList(removableReviewers())) {
|
||||
removable.add(a._accountId());
|
||||
}
|
||||
}
|
||||
return removable;
|
||||
}
|
||||
|
||||
public final native String id() /*-{ return this.id; }-*/;
|
||||
public final native String project() /*-{ return this.project; }-*/;
|
||||
public final native String branch() /*-{ return this.branch; }-*/;
|
||||
|
@ -48,20 +48,26 @@ import java.util.Set;
|
||||
/** Displays a table of label and reviewer scores. */
|
||||
class Labels extends Grid {
|
||||
private static final String DATA_ID = "data-id";
|
||||
private static final String REMOVE;
|
||||
private static final String DATA_VOTE = "data-vote";
|
||||
private static final String REMOVE_REVIEWER;
|
||||
private static final String REMOVE_VOTE;
|
||||
|
||||
static {
|
||||
REMOVE = DOM.createUniqueId().replace('-', '_');
|
||||
init(REMOVE);
|
||||
REMOVE_REVIEWER = DOM.createUniqueId().replace('-', '_');
|
||||
REMOVE_VOTE = DOM.createUniqueId().replace('-', '_');
|
||||
init(REMOVE_REVIEWER, REMOVE_VOTE);
|
||||
}
|
||||
|
||||
private static final native void init(String r) /*-{
|
||||
private static final native void init(String r, String v) /*-{
|
||||
$wnd[r] = $entry(function(e) {
|
||||
@com.google.gerrit.client.change.Labels::onRemove(Lcom/google/gwt/dom/client/NativeEvent;)(e)
|
||||
@com.google.gerrit.client.change.Labels::onRemoveReviewer(Lcom/google/gwt/dom/client/NativeEvent;)(e)
|
||||
});
|
||||
$wnd[v] = $entry(function(e) {
|
||||
@com.google.gerrit.client.change.Labels::onRemoveVote(Lcom/google/gwt/dom/client/NativeEvent;)(e)
|
||||
});
|
||||
}-*/;
|
||||
|
||||
private static void onRemove(NativeEvent event) {
|
||||
private static void onRemoveReviewer(NativeEvent event) {
|
||||
Integer user = getDataId(event);
|
||||
if (user != null) {
|
||||
final ChangeScreen screen = ChangeScreen.get(event);
|
||||
@ -77,6 +83,23 @@ class Labels extends Grid {
|
||||
}
|
||||
}
|
||||
|
||||
private static void onRemoveVote(NativeEvent event) {
|
||||
Integer user = getDataId(event);
|
||||
String vote = getVoteId(event);
|
||||
if (user != null && vote != null) {
|
||||
final ChangeScreen screen = ChangeScreen.get(event);
|
||||
ChangeApi.vote(screen.getChangeId().get(), user, vote).delete(
|
||||
new GerritCallback<JavaScriptObject>() {
|
||||
@Override
|
||||
public void onSuccess(JavaScriptObject result) {
|
||||
if (screen.isCurrentView()) {
|
||||
Gerrit.display(PageLinks.toChange(screen.getChangeId()));
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private static Integer getDataId(NativeEvent event) {
|
||||
Element e = event.getEventTarget().cast();
|
||||
while (e != null) {
|
||||
@ -89,6 +112,18 @@ class Labels extends Grid {
|
||||
return null;
|
||||
}
|
||||
|
||||
private static String getVoteId(NativeEvent event) {
|
||||
Element e = event.getEventTarget().cast();
|
||||
while (e != null) {
|
||||
String v = e.getAttribute(DATA_VOTE);
|
||||
if (!v.isEmpty()) {
|
||||
return v;
|
||||
}
|
||||
e = e.getParentElement();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private ChangeScreen.Style style;
|
||||
|
||||
void init(ChangeScreen.Style style) {
|
||||
@ -97,6 +132,7 @@ class Labels extends Grid {
|
||||
|
||||
void set(ChangeInfo info) {
|
||||
List<String> names = new ArrayList<>(info.labels());
|
||||
Set<Integer> removable = info.removableReviewerIds();
|
||||
Collections.sort(names);
|
||||
|
||||
resize(names.size(), 2);
|
||||
@ -106,14 +142,14 @@ class Labels extends Grid {
|
||||
LabelInfo label = info.label(name);
|
||||
setText(row, 0, name);
|
||||
if (label.all() != null) {
|
||||
setWidget(row, 1, renderUsers(label));
|
||||
setWidget(row, 1, renderUsers(label, removable));
|
||||
}
|
||||
getCellFormatter().setStyleName(row, 0, style.labelName());
|
||||
getCellFormatter().addStyleName(row, 0, getStyleForLabel(label));
|
||||
}
|
||||
}
|
||||
|
||||
private Widget renderUsers(LabelInfo label) {
|
||||
private Widget renderUsers(LabelInfo label, Set<Integer> removable) {
|
||||
Map<Integer, List<ApprovalInfo>> m = new HashMap<>(4);
|
||||
int approved = 0;
|
||||
int rejected = 0;
|
||||
@ -150,8 +186,8 @@ class Labels extends Grid {
|
||||
html.setStyleName(style.label_reject());
|
||||
}
|
||||
html.append(val).append(" ");
|
||||
html.append(formatUserList(style, m.get(v),
|
||||
Collections.<Integer> emptySet(), null));
|
||||
html.append(formatUserList(style, m.get(v), removable,
|
||||
label.name(), null));
|
||||
html.closeSpan();
|
||||
}
|
||||
return html.toBlockWidget();
|
||||
@ -198,6 +234,7 @@ class Labels extends Grid {
|
||||
static SafeHtml formatUserList(ChangeScreen.Style style,
|
||||
Collection<? extends AccountInfo> in,
|
||||
Set<Integer> removable,
|
||||
String label,
|
||||
Map<Integer, VotableInfo> votable) {
|
||||
List<AccountInfo> users = new ArrayList<>(in);
|
||||
Collections.sort(users, new Comparator<AccountInfo>() {
|
||||
@ -257,6 +294,9 @@ class Labels extends Grid {
|
||||
.setAttribute(DATA_ID, ai._accountId())
|
||||
.setAttribute("title", getTitle(ai, votableCategories))
|
||||
.setStyleName(style.label_user());
|
||||
if (label != null) {
|
||||
html.setAttribute(DATA_VOTE, label);
|
||||
}
|
||||
if (img != null) {
|
||||
html.openElement("img")
|
||||
.setStyleName(style.avatar())
|
||||
@ -271,10 +311,15 @@ class Labels extends Grid {
|
||||
}
|
||||
html.append(name);
|
||||
if (removable.contains(ai._accountId())) {
|
||||
html.openElement("button")
|
||||
.setAttribute("title", Util.M.removeReviewer(name))
|
||||
.setAttribute("onclick", REMOVE + "(event)")
|
||||
.append("×")
|
||||
html.openElement("button");
|
||||
if (label != null) {
|
||||
html.setAttribute("title", Util.M.removeVote(label))
|
||||
.setAttribute("onclick", REMOVE_VOTE + "(event)");
|
||||
} else {
|
||||
html.setAttribute("title", Util.M.removeReviewer(name))
|
||||
.setAttribute("onclick", REMOVE_REVIEWER + "(event)");
|
||||
}
|
||||
html.append("×")
|
||||
.closeElement("button");
|
||||
}
|
||||
html.closeSpan();
|
||||
|
@ -51,7 +51,6 @@ import com.google.gwtexpui.safehtml.client.SafeHtml;
|
||||
import com.google.gwtexpui.safehtml.client.SafeHtmlBuilder;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
@ -214,20 +213,13 @@ public class Reviewers extends Composite {
|
||||
cc.remove(i);
|
||||
}
|
||||
cc.remove(info.owner()._accountId());
|
||||
|
||||
Set<Integer> removable = new HashSet<>();
|
||||
if (info.removableReviewers() != null) {
|
||||
for (AccountInfo a : Natives.asList(info.removableReviewers())) {
|
||||
removable.add(a._accountId());
|
||||
}
|
||||
}
|
||||
|
||||
Set<Integer> removable = info.removableReviewerIds();
|
||||
Map<Integer, VotableInfo> votable = votable(info);
|
||||
|
||||
SafeHtml rHtml = Labels.formatUserList(style,
|
||||
r.values(), removable, votable);
|
||||
r.values(), removable, null, votable);
|
||||
SafeHtml ccHtml = Labels.formatUserList(style,
|
||||
cc.values(), removable, votable);
|
||||
cc.values(), removable, null, votable);
|
||||
|
||||
reviewersText.setInnerSafeHtml(rHtml);
|
||||
ccText.setInnerSafeHtml(ccHtml);
|
||||
|
@ -155,6 +155,10 @@ public class ChangeApi {
|
||||
.addParameter("n", n);
|
||||
}
|
||||
|
||||
public static RestApi vote(int id, int reviewer, String vote) {
|
||||
return reviewer(id, reviewer).view("votes").id(vote);
|
||||
}
|
||||
|
||||
public static RestApi reviewer(int id, int reviewer) {
|
||||
return change(id).view("reviewers").id(reviewer);
|
||||
}
|
||||
|
@ -43,6 +43,7 @@ public interface ChangeMessages extends Messages {
|
||||
|
||||
String removeHashtag(String name);
|
||||
String removeReviewer(String fullName);
|
||||
String removeVote(String label);
|
||||
String messageWrittenOn(String date);
|
||||
|
||||
String renamedFrom(String sourcePath);
|
||||
|
@ -24,6 +24,7 @@ patchTableSize_Lines = {0} lines
|
||||
|
||||
removeHashtag = Remove hashtag {0}
|
||||
removeReviewer = Remove reviewer {0}
|
||||
removeVote = Remove vote {0}
|
||||
messageWrittenOn = on {0}
|
||||
|
||||
renamedFrom = renamed from {0}
|
||||
|
@ -22,6 +22,7 @@ import com.google.gerrit.extensions.api.changes.FixInput;
|
||||
import com.google.gerrit.extensions.api.changes.HashtagsInput;
|
||||
import com.google.gerrit.extensions.api.changes.RestoreInput;
|
||||
import com.google.gerrit.extensions.api.changes.RevertInput;
|
||||
import com.google.gerrit.extensions.api.changes.ReviewerApi;
|
||||
import com.google.gerrit.extensions.api.changes.RevisionApi;
|
||||
import com.google.gerrit.extensions.client.ListChangesOption;
|
||||
import com.google.gerrit.extensions.common.ChangeInfo;
|
||||
@ -46,6 +47,7 @@ import com.google.gerrit.server.change.PostReviewers;
|
||||
import com.google.gerrit.server.change.PutTopic;
|
||||
import com.google.gerrit.server.change.Restore;
|
||||
import com.google.gerrit.server.change.Revert;
|
||||
import com.google.gerrit.server.change.Reviewers;
|
||||
import com.google.gerrit.server.change.Revisions;
|
||||
import com.google.gerrit.server.change.SubmittedTogether;
|
||||
import com.google.gerrit.server.change.SuggestReviewers;
|
||||
@ -68,7 +70,9 @@ class ChangeApiImpl implements ChangeApi {
|
||||
|
||||
private final Provider<CurrentUser> user;
|
||||
private final Changes changeApi;
|
||||
private final Reviewers reviewers;
|
||||
private final Revisions revisions;
|
||||
private final ReviewerApiImpl.Factory reviewerApi;
|
||||
private final RevisionApiImpl.Factory revisionApi;
|
||||
private final Provider<SuggestReviewers> suggestReviewers;
|
||||
private final ChangeResource change;
|
||||
@ -90,7 +94,9 @@ class ChangeApiImpl implements ChangeApi {
|
||||
@Inject
|
||||
ChangeApiImpl(Provider<CurrentUser> user,
|
||||
Changes changeApi,
|
||||
Reviewers reviewers,
|
||||
Revisions revisions,
|
||||
ReviewerApiImpl.Factory reviewerApi,
|
||||
RevisionApiImpl.Factory revisionApi,
|
||||
Provider<SuggestReviewers> suggestReviewers,
|
||||
Abandon abandon,
|
||||
@ -111,7 +117,9 @@ class ChangeApiImpl implements ChangeApi {
|
||||
this.user = user;
|
||||
this.changeApi = changeApi;
|
||||
this.revert = revert;
|
||||
this.reviewers = reviewers;
|
||||
this.revisions = revisions;
|
||||
this.reviewerApi = reviewerApi;
|
||||
this.revisionApi = revisionApi;
|
||||
this.suggestReviewers = suggestReviewers;
|
||||
this.abandon = abandon;
|
||||
@ -155,6 +163,16 @@ class ChangeApiImpl implements ChangeApi {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public ReviewerApi reviewer(String id) throws RestApiException {
|
||||
try {
|
||||
return reviewerApi.create(
|
||||
reviewers.parse(change, IdString.fromDecoded(id)));
|
||||
} catch (OrmException e) {
|
||||
throw new RestApiException("Cannot parse reviewer", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void abandon() throws RestApiException {
|
||||
abandon(new AbandonInput());
|
||||
|
@ -27,5 +27,6 @@ public class Module extends FactoryModule {
|
||||
factory(DraftApiImpl.Factory.class);
|
||||
factory(RevisionApiImpl.Factory.class);
|
||||
factory(FileApiImpl.Factory.class);
|
||||
factory(ReviewerApiImpl.Factory.class);
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,65 @@
|
||||
// Copyright (C) 2014 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.ReviewerApi;
|
||||
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 ReviewerApiImpl implements ReviewerApi {
|
||||
interface Factory {
|
||||
ReviewerApiImpl create(ReviewerResource r);
|
||||
}
|
||||
|
||||
private final ReviewerResource reviewer;
|
||||
private final Votes.List listVotes;
|
||||
private final DeleteVote deleteVote;
|
||||
|
||||
@Inject
|
||||
ReviewerApiImpl(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);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,151 @@
|
||||
// Copyright (C) 2014 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.common.TimeUtil;
|
||||
import com.google.gerrit.extensions.restapi.AuthException;
|
||||
import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
|
||||
import com.google.gerrit.extensions.restapi.Response;
|
||||
import com.google.gerrit.extensions.restapi.RestApiException;
|
||||
import com.google.gerrit.extensions.restapi.RestModifyView;
|
||||
import com.google.gerrit.reviewdb.client.Account;
|
||||
import com.google.gerrit.reviewdb.client.Change;
|
||||
import com.google.gerrit.reviewdb.client.ChangeMessage;
|
||||
import com.google.gerrit.reviewdb.client.PatchSet;
|
||||
import com.google.gerrit.reviewdb.client.PatchSetApproval;
|
||||
import com.google.gerrit.reviewdb.server.ReviewDb;
|
||||
import com.google.gerrit.server.ApprovalsUtil;
|
||||
import com.google.gerrit.server.ChangeMessagesUtil;
|
||||
import com.google.gerrit.server.ChangeUtil;
|
||||
import com.google.gerrit.server.IdentifiedUser;
|
||||
import com.google.gerrit.server.change.DeleteVote.Input;
|
||||
import com.google.gerrit.server.git.BatchUpdate;
|
||||
import com.google.gerrit.server.git.BatchUpdate.ChangeContext;
|
||||
import com.google.gerrit.server.git.UpdateException;
|
||||
import com.google.gerrit.server.project.ChangeControl;
|
||||
import com.google.gwtorm.server.OrmException;
|
||||
import com.google.inject.Inject;
|
||||
import com.google.inject.Provider;
|
||||
import com.google.inject.Singleton;
|
||||
|
||||
import java.util.Collections;
|
||||
|
||||
@Singleton
|
||||
public class DeleteVote implements RestModifyView<VoteResource, Input> {
|
||||
public static class Input {
|
||||
}
|
||||
|
||||
private final Provider<ReviewDb> db;
|
||||
private final BatchUpdate.Factory batchUpdateFactory;
|
||||
private final ApprovalsUtil approvalsUtil;
|
||||
private final ChangeMessagesUtil cmUtil;
|
||||
private final IdentifiedUser.GenericFactory userFactory;
|
||||
|
||||
@Inject
|
||||
DeleteVote(Provider<ReviewDb> db,
|
||||
BatchUpdate.Factory batchUpdateFactory,
|
||||
ApprovalsUtil approvalsUtil,
|
||||
ChangeMessagesUtil cmUtil,
|
||||
IdentifiedUser.GenericFactory userFactory) {
|
||||
this.db = db;
|
||||
this.batchUpdateFactory = batchUpdateFactory;
|
||||
this.approvalsUtil = approvalsUtil;
|
||||
this.cmUtil = cmUtil;
|
||||
this.userFactory = userFactory;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Response<?> apply(VoteResource rsrc, Input input)
|
||||
throws RestApiException, UpdateException {
|
||||
ReviewerResource r = rsrc.getReviewer();
|
||||
ChangeControl ctl = r.getControl();
|
||||
Change change = r.getChange();
|
||||
try (BatchUpdate bu = batchUpdateFactory.create(db.get(),
|
||||
change.getProject(), ctl.getUser().asIdentifiedUser(),
|
||||
TimeUtil.nowTs())) {
|
||||
bu.addOp(change.getId(),
|
||||
new Op(r.getUser().getAccountId(), rsrc.getLabel()));
|
||||
bu.execute();
|
||||
}
|
||||
|
||||
return Response.none();
|
||||
}
|
||||
|
||||
private class Op extends BatchUpdate.Op {
|
||||
private final Account.Id accountId;
|
||||
private final String label;
|
||||
|
||||
private Op(Account.Id accountId, String label) {
|
||||
this.accountId = accountId;
|
||||
this.label = label;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateChange(ChangeContext ctx)
|
||||
throws OrmException, AuthException, ResourceNotFoundException {
|
||||
IdentifiedUser user = ctx.getUser().asIdentifiedUser();
|
||||
Change change = ctx.getChange();
|
||||
ChangeControl ctl = ctx.getChangeControl();
|
||||
PatchSet.Id psId = change.currentPatchSetId();
|
||||
|
||||
PatchSetApproval psa = null;
|
||||
StringBuilder msg = new StringBuilder();
|
||||
for (PatchSetApproval a : approvalsUtil.byPatchSetUser(
|
||||
ctx.getDb(), ctl, psId, accountId)) {
|
||||
if (ctl.canRemoveReviewer(a)) {
|
||||
if (a.getLabel().equals(label)) {
|
||||
msg.append("Removed ")
|
||||
.append(a.getLabel()).append(formatLabelValue(a.getValue()))
|
||||
.append(" by ").append(userFactory.create(user.getAccountId())
|
||||
.getNameEmail())
|
||||
.append("\n");
|
||||
psa = a;
|
||||
a.setValue((short)0);
|
||||
ctx.getChangeUpdate().setPatchSetId(psId);
|
||||
ctx.getChangeUpdate().removeApproval(label);
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
throw new AuthException("delete not permitted");
|
||||
}
|
||||
}
|
||||
if (psa == null) {
|
||||
throw new ResourceNotFoundException();
|
||||
}
|
||||
ChangeUtil.bumpRowVersionNotLastUpdatedOn(change.getId(), ctx.getDb());
|
||||
ctx.getDb().patchSetApprovals().update(Collections.singleton(psa));
|
||||
|
||||
if (msg.length() > 0) {
|
||||
ChangeMessage changeMessage =
|
||||
new ChangeMessage(new ChangeMessage.Key(change.getId(),
|
||||
ChangeUtil.messageUUID(ctx.getDb())),
|
||||
user.getAccountId(),
|
||||
ctx.getWhen(),
|
||||
change.currentPatchSetId());
|
||||
changeMessage.setMessage(msg.toString());
|
||||
cmUtil.addChangeMessage(ctx.getDb(), ctx.getChangeUpdate(),
|
||||
changeMessage);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static String formatLabelValue(short value) {
|
||||
if (value > 0) {
|
||||
return "+" + value;
|
||||
} else {
|
||||
return Short.toString(value);
|
||||
}
|
||||
}
|
||||
}
|
@ -21,6 +21,7 @@ import static com.google.gerrit.server.change.DraftCommentResource.DRAFT_COMMENT
|
||||
import static com.google.gerrit.server.change.FileResource.FILE_KIND;
|
||||
import static com.google.gerrit.server.change.ReviewerResource.REVIEWER_KIND;
|
||||
import static com.google.gerrit.server.change.RevisionResource.REVISION_KIND;
|
||||
import static com.google.gerrit.server.change.VoteResource.VOTE_KIND;
|
||||
|
||||
import com.google.gerrit.extensions.registration.DynamicMap;
|
||||
import com.google.gerrit.extensions.restapi.RestApiModule;
|
||||
@ -37,6 +38,7 @@ public class Module extends RestApiModule {
|
||||
bind(DraftComments.class);
|
||||
bind(Comments.class);
|
||||
bind(Files.class);
|
||||
bind(Votes.class);
|
||||
|
||||
DynamicMap.mapOf(binder(), CHANGE_KIND);
|
||||
DynamicMap.mapOf(binder(), COMMENT_KIND);
|
||||
@ -45,6 +47,7 @@ public class Module extends RestApiModule {
|
||||
DynamicMap.mapOf(binder(), REVIEWER_KIND);
|
||||
DynamicMap.mapOf(binder(), REVISION_KIND);
|
||||
DynamicMap.mapOf(binder(), CHANGE_EDIT_KIND);
|
||||
DynamicMap.mapOf(binder(), VOTE_KIND);
|
||||
|
||||
get(CHANGE_KIND).to(GetChange.class);
|
||||
get(CHANGE_KIND, "detail").to(GetDetail.class);
|
||||
@ -73,6 +76,8 @@ public class Module extends RestApiModule {
|
||||
child(CHANGE_KIND, "reviewers").to(Reviewers.class);
|
||||
get(REVIEWER_KIND).to(GetReviewer.class);
|
||||
delete(REVIEWER_KIND).to(DeleteReviewer.class);
|
||||
child(REVIEWER_KIND, "votes").to(Votes.class);
|
||||
delete(VOTE_KIND).to(DeleteVote.class);
|
||||
|
||||
child(CHANGE_KIND, "revisions").to(Revisions.class);
|
||||
get(REVISION_KIND, "actions").to(GetRevisionActions.class);
|
||||
|
@ -0,0 +1,40 @@
|
||||
// Copyright (C) 2014 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.restapi.RestResource;
|
||||
import com.google.gerrit.extensions.restapi.RestView;
|
||||
import com.google.inject.TypeLiteral;
|
||||
|
||||
public class VoteResource implements RestResource {
|
||||
public static final TypeLiteral<RestView<VoteResource>> VOTE_KIND =
|
||||
new TypeLiteral<RestView<VoteResource>>() {};
|
||||
|
||||
private final ReviewerResource reviewer;
|
||||
private final String label;
|
||||
|
||||
public VoteResource(ReviewerResource reviewer, String label) {
|
||||
this.reviewer = reviewer;
|
||||
this.label = label;
|
||||
}
|
||||
|
||||
public ReviewerResource getReviewer() {
|
||||
return reviewer;
|
||||
}
|
||||
|
||||
public String getLabel() {
|
||||
return label;
|
||||
}
|
||||
}
|
@ -0,0 +1,89 @@
|
||||
// Copyright (C) 2014 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.ResourceNotFoundException;
|
||||
import com.google.gerrit.extensions.restapi.RestReadView;
|
||||
import com.google.gerrit.extensions.restapi.RestView;
|
||||
import com.google.gerrit.reviewdb.client.PatchSetApproval;
|
||||
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.Map;
|
||||
import java.util.TreeMap;
|
||||
|
||||
@Singleton
|
||||
public class Votes implements ChildCollection<ReviewerResource, VoteResource> {
|
||||
private final DynamicMap<RestView<VoteResource>> views;
|
||||
private final List list;
|
||||
|
||||
@Inject
|
||||
Votes(DynamicMap<RestView<VoteResource>> views,
|
||||
List list) {
|
||||
this.views = views;
|
||||
this.list = list;
|
||||
}
|
||||
|
||||
@Override
|
||||
public DynamicMap<RestView<VoteResource>> views() {
|
||||
return views;
|
||||
}
|
||||
|
||||
@Override
|
||||
public RestView<ReviewerResource> list() throws AuthException {
|
||||
return list;
|
||||
}
|
||||
|
||||
@Override
|
||||
public VoteResource parse(ReviewerResource reviewer, IdString id)
|
||||
throws ResourceNotFoundException, OrmException, AuthException {
|
||||
return new VoteResource(reviewer, id.get());
|
||||
}
|
||||
|
||||
@Singleton
|
||||
public static class List implements RestReadView<ReviewerResource> {
|
||||
private final Provider<ReviewDb> db;
|
||||
private final ApprovalsUtil approvalsUtil;
|
||||
|
||||
@Inject
|
||||
List(Provider<ReviewDb> db,
|
||||
ApprovalsUtil approvalsUtil) {
|
||||
this.db = db;
|
||||
this.approvalsUtil = approvalsUtil;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, Short> apply(ReviewerResource rsrc) throws OrmException {
|
||||
Map<String, Short> votes = new TreeMap<>();
|
||||
Iterable<PatchSetApproval> byPatchSetUser = approvalsUtil.byPatchSetUser(
|
||||
db.get(),
|
||||
rsrc.getControl(),
|
||||
rsrc.getChange().currentPatchSetId(),
|
||||
rsrc.getUser().getAccountId());
|
||||
for (PatchSetApproval psa : byPatchSetUser) {
|
||||
votes.put(psa.getLabel(), psa.getValue());
|
||||
}
|
||||
return votes;
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user