Set Assignee from ChangeScreen

Allows setting of assignee from ChangeScreen.

First iteration. In later iterations we should implement
support for easier picking of previous assignees of the
change.

The new user_edit.png icon is from the same icon package
(Silk Icons) already used elsewhere in Gerrit.

Change-Id: I38b92cf35f806dd9b757e599b806fa4d24a3a058
diff --git a/gerrit-gwtui-common/src/main/java/com/google/gerrit/client/Resources.java b/gerrit-gwtui-common/src/main/java/com/google/gerrit/client/Resources.java
index 95751fa..c8e23e5 100644
--- a/gerrit-gwtui-common/src/main/java/com/google/gerrit/client/Resources.java
+++ b/gerrit-gwtui-common/src/main/java/com/google/gerrit/client/Resources.java
@@ -30,6 +30,9 @@
   @Source("user_add.png")
   ImageResource addUser();
 
+  @Source("user_edit.png")
+  ImageResource editUser();
+
   // derived from resultset_next.png
   @Source("resultset_down_gray.png")
   ImageResource arrowDown();
diff --git a/gerrit-gwtui-common/src/main/java/com/google/gerrit/client/info/ChangeInfo.java b/gerrit-gwtui-common/src/main/java/com/google/gerrit/client/info/ChangeInfo.java
index b29a3de..df12c29 100644
--- a/gerrit-gwtui-common/src/main/java/com/google/gerrit/client/info/ChangeInfo.java
+++ b/gerrit-gwtui-common/src/main/java/com/google/gerrit/client/info/ChangeInfo.java
@@ -115,6 +115,7 @@
   private native String statusRaw() /*-{ return this.status; }-*/;
   public final native String subject() /*-{ return this.subject; }-*/;
   public final native AccountInfo owner() /*-{ return this.owner; }-*/;
+  public final native AccountInfo assignee() /*-{ return this.assignee; }-*/;
   private native String createdRaw() /*-{ return this.created; }-*/;
   private native String updatedRaw() /*-{ return this.updated; }-*/;
   private native String submittedRaw() /*-{ return this.submitted; }-*/;
diff --git a/gerrit-gwtui-common/src/main/resources/com/google/gerrit/client/user_edit.png b/gerrit-gwtui-common/src/main/resources/com/google/gerrit/client/user_edit.png
new file mode 100644
index 0000000..c1974cd
--- /dev/null
+++ b/gerrit-gwtui-common/src/main/resources/com/google/gerrit/client/user_edit.png
Binary files differ
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/Assignee.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/Assignee.java
new file mode 100644
index 0000000..f46a3d1
--- /dev/null
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/Assignee.java
@@ -0,0 +1,171 @@
+// Copyright (C) 2016 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.client.change;
+
+import com.google.gerrit.client.Gerrit;
+import com.google.gerrit.client.NotSignedInDialog;
+import com.google.gerrit.client.changes.ChangeApi;
+import com.google.gerrit.client.changes.Util;
+import com.google.gerrit.client.info.AccountInfo;
+import com.google.gerrit.client.info.ChangeInfo;
+import com.google.gerrit.client.rpc.GerritCallback;
+import com.google.gerrit.client.ui.InlineHyperlink;
+import com.google.gerrit.client.ui.RemoteSuggestBox;
+import com.google.gerrit.reviewdb.client.Change;
+import com.google.gwt.core.client.GWT;
+import com.google.gwt.dom.client.Element;
+import com.google.gwt.event.dom.client.ClickEvent;
+import com.google.gwt.event.dom.client.ClickHandler;
+import com.google.gwt.event.logical.shared.CloseEvent;
+import com.google.gwt.event.logical.shared.CloseHandler;
+import com.google.gwt.event.logical.shared.SelectionEvent;
+import com.google.gwt.event.logical.shared.SelectionHandler;
+import com.google.gwt.uibinder.client.UiBinder;
+import com.google.gwt.uibinder.client.UiField;
+import com.google.gwt.uibinder.client.UiHandler;
+import com.google.gwt.user.client.rpc.StatusCodeException;
+import com.google.gwt.user.client.ui.Composite;
+import com.google.gwt.user.client.ui.HTMLPanel;
+import com.google.gwt.user.client.ui.Image;
+import com.google.gwt.user.client.ui.UIObject;
+
+/**
+ * Edit assignee using auto-completion.
+ */
+public class Assignee extends Composite {
+  interface Binder extends UiBinder<HTMLPanel, Assignee> {
+  }
+
+  private static final Binder uiBinder = GWT.create(Binder.class);
+
+  @UiField
+  InlineHyperlink assigneeLink;
+  @UiField
+  Image editAssigneeIcon;
+  @UiField
+  Element form;
+  @UiField
+  Element error;
+  @UiField(provided = true)
+  RemoteSuggestBox suggestBox;
+
+  private AssigneeSuggestOracle assigneeSuggestOracle;
+  private Change.Id changeId;
+
+  Assignee() {
+    assigneeSuggestOracle = new AssigneeSuggestOracle();
+    suggestBox = new RemoteSuggestBox(assigneeSuggestOracle);
+    suggestBox.setVisibleLength(55);
+    suggestBox.setHintText(Util.C.approvalTableEditAssigneeHint());
+    suggestBox.addCloseHandler(new CloseHandler<RemoteSuggestBox>() {
+      @Override
+      public void onClose(CloseEvent<RemoteSuggestBox> event) {
+        Assignee.this.onCancel(null);
+      }
+    });
+    suggestBox.addSelectionHandler(new SelectionHandler<String>() {
+      @Override
+      public void onSelection(SelectionEvent<String> event) {
+        editAssignee(event.getSelectedItem());
+      }
+    });
+
+    initWidget(uiBinder.createAndBindUi(this));
+    editAssigneeIcon.addDomHandler(new ClickHandler() {
+      @Override
+      public void onClick(ClickEvent event) {
+        onOpenForm();
+      }
+    }, ClickEvent.getType());
+  }
+
+  void set(ChangeInfo info) {
+    this.changeId = info.legacyId();
+    assigneeLink.setText(info.assignee() != null ? info.assignee().name() : "");
+    assigneeSuggestOracle.setChange(changeId);
+    editAssigneeIcon.setVisible(Gerrit.isSignedIn());
+  }
+
+  void onOpenForm() {
+    UIObject.setVisible(form, true);
+    UIObject.setVisible(error, false);
+    editAssigneeIcon.setVisible(false);
+    suggestBox.setFocus(true);
+    suggestBox.setText("");
+  }
+
+  void onCloseForm() {
+    UIObject.setVisible(form, false);
+    UIObject.setVisible(error, false);
+    editAssigneeIcon.setVisible(true);
+    suggestBox.setFocus(false);
+  }
+
+  @UiHandler("assign")
+  void onEditAssignee(@SuppressWarnings("unused") ClickEvent e) {
+    editAssignee(suggestBox.getText());
+  }
+
+  @UiHandler("cancel")
+  void onCancel(@SuppressWarnings("unused") ClickEvent e) {
+    onCloseForm();
+  }
+
+  private void editAssignee(final String assignee) {
+    if (assignee.isEmpty()) {
+      ChangeApi.deleteAssignee(changeId.get(),
+          new GerritCallback<AccountInfo>() {
+            @Override
+            public void onSuccess(AccountInfo result) {
+              onCloseForm();
+              assigneeLink.setText("");
+            }
+
+            @Override
+            public void onFailure(Throwable err) {
+              if (isSigninFailure(err)) {
+                new NotSignedInDialog().center();
+              } else {
+                UIObject.setVisible(error, true);
+                error.setInnerText(err instanceof StatusCodeException
+                    ? ((StatusCodeException) err).getEncodedResponse()
+                    : err.getMessage());
+              }
+            }
+          });
+    } else {
+      ChangeApi.setAssignee(changeId.get(), assignee,
+          new GerritCallback<AccountInfo>() {
+            @Override
+            public void onSuccess(AccountInfo result) {
+              onCloseForm();
+              assigneeLink.setText(result.name());
+            }
+
+            @Override
+            public void onFailure(Throwable err) {
+              if (isSigninFailure(err)) {
+                new NotSignedInDialog().center();
+              } else {
+                UIObject.setVisible(error, true);
+                error.setInnerText(err instanceof StatusCodeException
+                    ? ((StatusCodeException) err).getEncodedResponse()
+                    : err.getMessage());
+              }
+            }
+          });
+    }
+  }
+}
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/Assignee.ui.xml b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/Assignee.ui.xml
new file mode 100644
index 0000000..965ab2c
--- /dev/null
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/Assignee.ui.xml
@@ -0,0 +1,65 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+Copyright (C) 2016 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.
+-->
+<ui:UiBinder
+    xmlns:ui='urn:ui:com.google.gwt.uibinder'
+    xmlns:c='urn:import:com.google.gwtexpui.globalkey.client'
+    xmlns:g='urn:import:com.google.gwt.user.client.ui'
+    xmlns:u='urn:import:com.google.gerrit.client.ui'>
+  <ui:with field='ico' type='com.google.gerrit.client.GerritResources'/>
+  <ui:with field='res' type='com.google.gerrit.client.change.Resources'/>
+  <ui:style gss='false'>
+    .suggestBox {
+      margin-bottom: 2px;
+    }
+
+    .error {
+      color: #D33D3D;
+      font-weight: bold;
+    }
+
+    .editAssignee,
+    .cancel {
+      cursor: pointer;
+      float: right;
+    }
+  </ui:style>
+  <g:HTMLPanel>
+    <div>
+      <u:InlineHyperlink ui:field='assigneeLink'/>
+      <g:Image ui:field='editAssigneeIcon'
+          resource='{ico.editUser}'
+          styleName='{style.editAssignee}'
+          title='Assign User to Change'/>
+    </div>
+    <div ui:field='form' style='display: none' aria-hidden='true'>
+      <u:RemoteSuggestBox ui:field='suggestBox' styleName='{style.suggestBox}'/>
+      <div ui:field='error'
+           class='{style.error}'
+           style='display: none' aria-hidden='true'/>
+      <div>
+        <g:Button ui:field='assign' styleName='{res.style.button}'>
+          <div>Assign</div>
+        </g:Button>
+        <g:Button ui:field='cancel'
+            styleName='{res.style.button}'
+            addStyleNames='{style.cancel}'>
+          <div>Cancel</div>
+        </g:Button>
+      </div>
+    </div>
+   </g:HTMLPanel>
+  </ui:UiBinder>
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/AssigneeSuggestOracle.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/AssigneeSuggestOracle.java
new file mode 100644
index 0000000..8dc5574
--- /dev/null
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/AssigneeSuggestOracle.java
@@ -0,0 +1,59 @@
+// Copyright (C) 2016 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.client.change;
+
+import com.google.gerrit.client.change.ReviewerSuggestOracle.RestReviewerSuggestion;
+import com.google.gerrit.client.change.ReviewerSuggestOracle.SuggestReviewerInfo;
+import com.google.gerrit.client.changes.ChangeApi;
+import com.google.gerrit.client.rpc.GerritCallback;
+import com.google.gerrit.client.rpc.Natives;
+import com.google.gerrit.client.ui.SuggestAfterTypingNCharsOracle;
+import com.google.gerrit.reviewdb.client.Change;
+import com.google.gwt.core.client.JsArray;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+/** REST API based suggestion Oracle for assignee */
+public class AssigneeSuggestOracle extends SuggestAfterTypingNCharsOracle {
+  private Change.Id changeId;
+
+  public void setChange(Change.Id changeId) {
+    this.changeId = changeId;
+  }
+
+  @Override
+  protected void _onRequestSuggestions(Request req, Callback cb) {
+    ChangeApi
+    .suggestReviewers(changeId.get(), req.getQuery(), req.getLimit(), true)
+    .get(new GerritCallback<JsArray<SuggestReviewerInfo>>() {
+      @Override
+      public void onSuccess(JsArray<SuggestReviewerInfo> result) {
+        List<RestReviewerSuggestion> r = new ArrayList<>(result.length());
+        for (SuggestReviewerInfo reviewer : Natives.asList(result)) {
+          r.add(new RestReviewerSuggestion(reviewer, req.getQuery()));
+        }
+        cb.onSuggestionsReady(req, new Response(r));
+      }
+
+      @Override
+      public void onFailure(Throwable err) {
+        List<Suggestion> r = Collections.emptyList();
+        cb.onSuggestionsReady(req, new Response(r));
+      }
+    });
+  }
+}
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/ChangeScreen.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/ChangeScreen.java
index 0ab989f..cc127bd 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/ChangeScreen.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/ChangeScreen.java
@@ -170,6 +170,7 @@
   @UiField ToggleButton star;
   @UiField Anchor permalink;
 
+  @UiField Assignee assignee;
   @UiField Element ccText;
   @UiField Reviewers reviewers;
   @UiField Hashtags hashtags;
@@ -1293,6 +1294,7 @@
     topic.set(info, revision);
     commit.set(commentLinkProcessor, info, revision);
     related.set(info, revision);
+    assignee.set(info);
     reviewers.set(info);
     if (Gerrit.isNoteDbEnabled()) {
       hashtags.set(info, revision);
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/ChangeScreen.ui.xml b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/ChangeScreen.ui.xml
index fe48eb9..6f4d813 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/ChangeScreen.ui.xml
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/ChangeScreen.ui.xml
@@ -464,6 +464,12 @@
               </td>
             </tr>
             <tr>
+              <th><ui:msg>Assignee</ui:msg></th>
+              <td>
+                <c:Assignee ui:field='assignee'/>
+              </td>
+            </tr>
+            <tr>
               <th><ui:msg>Reviewers</ui:msg></th>
               <td>
                 <c:Reviewers ui:field='reviewers'/>
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/ReviewerSuggestOracle.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/ReviewerSuggestOracle.java
index 2188c03..404f3c8 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/ReviewerSuggestOracle.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/ReviewerSuggestOracle.java
@@ -36,7 +36,8 @@
 
   @Override
   protected void _onRequestSuggestions(final Request req, final Callback cb) {
-    ChangeApi.suggestReviewers(changeId.get(), req.getQuery(), req.getLimit())
+    ChangeApi
+        .suggestReviewers(changeId.get(), req.getQuery(), req.getLimit(), false)
         .get(new GerritCallback<JsArray<SuggestReviewerInfo>>() {
           @Override
           public void onSuccess(JsArray<SuggestReviewerInfo> result) {
@@ -59,7 +60,7 @@
     this.changeId = changeId;
   }
 
-  private static class RestReviewerSuggestion implements Suggestion {
+  public static class RestReviewerSuggestion implements Suggestion {
     private final String displayString;
     private final String replacementString;
 
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeApi.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeApi.java
index 70d43e4..4882b97 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeApi.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeApi.java
@@ -15,6 +15,7 @@
 package com.google.gerrit.client.changes;
 
 import com.google.gerrit.client.Gerrit;
+import com.google.gerrit.client.info.AccountInfo;
 import com.google.gerrit.client.info.ChangeInfo;
 import com.google.gerrit.client.info.ChangeInfo.CommitInfo;
 import com.google.gerrit.client.info.ChangeInfo.EditInfo;
@@ -113,6 +114,17 @@
     return call(id, revision, "actions");
   }
 
+  public static void deleteAssignee(int id, AsyncCallback<AccountInfo> cb) {
+    change(id).view("assignee").delete(cb);
+  }
+
+  public static void setAssignee(int id, String user,
+      AsyncCallback<AccountInfo> cb) {
+    AssigneeInput input = AssigneeInput.create();
+    input.assignee(user);
+    change(id).view("assignee").put(user, cb);
+  }
+
   public static RestApi comments(int id) {
     return call(id, "comments");
   }
@@ -158,10 +170,11 @@
     return change(id).view("reviewers");
   }
 
-  public static RestApi suggestReviewers(int id, String q, int n) {
+  public static RestApi suggestReviewers(int id, String q, int n, boolean e) {
     return change(id).view("suggest_reviewers")
         .addParameter("q", q)
-        .addParameter("n", n);
+        .addParameter("n", n)
+        .addParameter("e", e);
   }
 
   public static RestApi vote(int id, int reviewer, String vote) {
@@ -262,6 +275,17 @@
     }
   }
 
+  private static class AssigneeInput extends JavaScriptObject {
+    final native void assignee(String a) /*-{ if(a)this.assignee=a; }-*/;
+
+    static AssigneeInput create() {
+      return (AssigneeInput) createObject();
+    }
+
+    protected AssigneeInput() {
+    }
+  }
+
   private static class TopicInput extends JavaScriptObject {
     final native void topic(String t) /*-{ if(t)this.topic=t; }-*/;
 
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeConstants.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeConstants.java
index 4a9eea4..89421b6 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeConstants.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeConstants.java
@@ -69,6 +69,8 @@
   String patchTableNext();
   String patchTableOpenDiff();
 
+  String approvalTableEditAssigneeHint();
+
   String approvalTableAddReviewerHint();
   String approvalTableAddManyReviewersConfirmationDialogTitle();
 
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeConstants.properties b/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeConstants.properties
index 675cd07..8dd307c 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeConstants.properties
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeConstants.properties
@@ -40,7 +40,6 @@
 keyExpandAllMessages = Expand all messages
 keyCollapseAllMessages = Collapse all messages
 
-
 patchTableColumnName = File Path
 patchTableColumnComments = Comments
 patchTableColumnSize = Size
@@ -51,6 +50,8 @@
 patchTableNext = Next file
 patchTableOpenDiff = Open diff
 
+approvalTableEditAssigneeHint = Name or Email
+
 approvalTableAddReviewerHint = Name or Email or Group
 approvalTableAddManyReviewersConfirmationDialogTitle = Adding Group Members as Reviewers
 
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/ReviewersUtil.java b/gerrit-server/src/main/java/com/google/gerrit/server/ReviewersUtil.java
index 5941c10..b01c233 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/ReviewersUtil.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/ReviewersUtil.java
@@ -121,7 +121,7 @@
 
   public List<SuggestedReviewerInfo> suggestReviewers(
       SuggestReviewers suggestReviewers, ProjectControl projectControl,
-      VisibilityControl visibilityControl)
+      VisibilityControl visibilityControl, boolean excludeGroups)
       throws IOException, OrmException, BadRequestException {
     String query = suggestReviewers.getQuery();
     boolean suggestAccounts = suggestReviewers.getSuggestAccounts();
@@ -147,20 +147,22 @@
       reviewer.add(info);
     }
 
-    for (GroupReference g : suggestAccountGroup(suggestReviewers, projectControl)) {
-      GroupAsReviewer result = suggestGroupAsReviewer(
-          suggestReviewers, projectControl.getProject(), g, visibilityControl);
-      if (result.allowed || result.allowedWithConfirmation) {
-        GroupBaseInfo info = new GroupBaseInfo();
-        info.id = Url.encode(g.getUUID().get());
-        info.name = g.getName();
-        SuggestedReviewerInfo suggestedReviewerInfo = new SuggestedReviewerInfo();
-        suggestedReviewerInfo.group = info;
-        suggestedReviewerInfo.count = result.size;
-        if (result.allowedWithConfirmation) {
-          suggestedReviewerInfo.confirm = true;
+    if (!excludeGroups) {
+      for (GroupReference g : suggestAccountGroup(suggestReviewers, projectControl)) {
+        GroupAsReviewer result = suggestGroupAsReviewer(
+            suggestReviewers, projectControl.getProject(), g, visibilityControl);
+        if (result.allowed || result.allowedWithConfirmation) {
+          GroupBaseInfo info = new GroupBaseInfo();
+          info.id = Url.encode(g.getUUID().get());
+          info.name = g.getName();
+          SuggestedReviewerInfo suggestedReviewerInfo = new SuggestedReviewerInfo();
+          suggestedReviewerInfo.group = info;
+          suggestedReviewerInfo.count = result.size;
+          if (result.allowedWithConfirmation) {
+            suggestedReviewerInfo.confirm = true;
+          }
+          reviewer.add(suggestedReviewerInfo);
         }
-        reviewer.add(suggestedReviewerInfo);
       }
     }
 
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/SuggestChangeReviewers.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/SuggestChangeReviewers.java
index 02d3afe..a1d53e0 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/SuggestChangeReviewers.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/SuggestChangeReviewers.java
@@ -30,12 +30,18 @@
 import com.google.inject.Provider;
 
 import org.eclipse.jgit.lib.Config;
+import org.kohsuke.args4j.Option;
 
 import java.io.IOException;
 import java.util.List;
 
 public class SuggestChangeReviewers extends SuggestReviewers
     implements RestReadView<ChangeResource> {
+
+  @Option(name = "--exclude-groups", aliases = {"-e"},
+      usage = "exclude groups from query")
+  boolean excludeGroups;
+
   @Inject
   SuggestChangeReviewers(AccountVisibility av,
       GenericFactory identifiedUserFactory,
@@ -49,7 +55,7 @@
   public List<SuggestedReviewerInfo> apply(ChangeResource rsrc)
       throws BadRequestException, OrmException, IOException {
     return reviewersUtil.suggestReviewers(this,
-        rsrc.getControl().getProjectControl(), getVisibility(rsrc));
+        rsrc.getControl().getProjectControl(), getVisibility(rsrc), excludeGroups);
   }
 
   private VisibilityControl getVisibility(final ChangeResource rsrc) {