ChangeScreen2: Support adding reviewers
Bug: issue 2066
Change-Id: I3654d019ce09c1b2c234c0b73f5d6c0e6c8ae0b9
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/ChangeScreen2.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/ChangeScreen2.java
index 09aef22..c6940de 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/ChangeScreen2.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/ChangeScreen2.java
@@ -120,7 +120,7 @@
@UiField AnchorElement permalink;
@UiField Element reviewersText;
- @UiField Element ccText;
+ @UiField Reviewers reviewers;
@UiField Element changeIdText;
@UiField Element ownerText;
@UiField Element statusText;
@@ -208,6 +208,7 @@
Resources.I.style().ensureInjected();
star.setVisible(Gerrit.isSignedIn());
labels.init(style, statusText);
+ reviewers.init(style);
keysNavigation = new KeyCommandSet(Gerrit.C.sectionNavigation());
keysNavigation.add(new KeyCommand(0, 'u', Util.C.upToChangeList()) {
@@ -241,6 +242,12 @@
star.setValue(!star.getValue(), true);
}
});
+ keysAction.add(new KeyCommand(0, 'c', Util.C.keyAddReviewers()) {
+ @Override
+ public void onKeyPress(KeyPressEvent event) {
+ reviewers.onOpenForm();
+ }
+ });
}
}
@@ -654,8 +661,9 @@
}
r.remove(info.owner()._account_id());
cc.remove(info.owner()._account_id());
- reviewersText.setInnerSafeHtml(labels.formatUserList(r.values()));
- ccText.setInnerSafeHtml(labels.formatUserList(cc.values()));
+ reviewersText.setInnerSafeHtml(Labels.formatUserList(style, r.values()));
+ reviewers.set(info.legacy_id());
+ reviewers.setReviewers(Labels.formatUserList(style, cc.values()));
}
private void renderOwner(ChangeInfo info) {
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/ChangeScreen2.ui.xml b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/ChangeScreen2.ui.xml
index 6cf7b7b..2c47c4d 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/ChangeScreen2.ui.xml
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/ChangeScreen2.ui.xml
@@ -281,7 +281,9 @@
</tr>
<tr>
<th><ui:msg>CC</ui:msg></th>
- <td ui:field='ccText'/>
+ <td>
+ <c:Reviewers ui:field='reviewers'/>
+ </td>
</tr>
<tr>
<th><ui:msg>Project</ui:msg></th>
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/Labels.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/Labels.java
index f3ce7fc..6316c64 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/Labels.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/Labels.java
@@ -123,7 +123,7 @@
html.setStyleName(style.label_reject());
}
html.append(val).append(" ");
- html.append(formatUserList(m.get(v)));
+ html.append(formatUserList(style, m.get(v)));
html.closeSpan();
}
return html.toBlockWidget();
@@ -167,7 +167,8 @@
}
}
- SafeHtml formatUserList(Collection<? extends AccountInfo> in) {
+ static SafeHtml formatUserList(ChangeScreen2.Style style,
+ Collection<? extends AccountInfo> in) {
List<AccountInfo> users = new ArrayList<AccountInfo>(in);
Collections.sort(users, new Comparator<AccountInfo>() {
@Override
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/RestReviewerSuggestOracle.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/RestReviewerSuggestOracle.java
new file mode 100644
index 0000000..7575869
--- /dev/null
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/RestReviewerSuggestOracle.java
@@ -0,0 +1,89 @@
+// Copyright (C) 2013 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.FormatUtil;
+import com.google.gerrit.client.account.AccountInfo;
+import com.google.gerrit.client.admin.Util;
+import com.google.gerrit.client.changes.ChangeApi;
+import com.google.gerrit.client.groups.GroupBaseInfo;
+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.JavaScriptObject;
+import com.google.gwt.core.client.JsArray;
+import com.google.gwt.user.client.ui.SuggestOracle;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/** REST API based suggestion Oracle for reviewers. */
+public class RestReviewerSuggestOracle extends SuggestAfterTypingNCharsOracle {
+
+ private Change.Id changeId;
+
+ @Override
+ protected void _onRequestSuggestions(final Request req, final Callback callback) {
+ ChangeApi.suggestReviewers(changeId.get(), req.getQuery(),
+ req.getLimit()).get(new GerritCallback<JsArray<SuggestReviewerInfo>>() {
+ @Override
+ public void onSuccess(JsArray<SuggestReviewerInfo> result) {
+ final List<RestReviewerSuggestion> r =
+ new ArrayList<RestReviewerSuggestion>(result.length());
+ for (final SuggestReviewerInfo reviewer : Natives.asList(result)) {
+ r.add(new RestReviewerSuggestion(reviewer));
+ }
+ callback.onSuggestionsReady(req, new Response(r));
+ }
+ });
+ }
+
+ public void setChange(Change.Id changeId) {
+ this.changeId = changeId;
+ }
+
+ private static class RestReviewerSuggestion implements SuggestOracle.Suggestion {
+ private final SuggestReviewerInfo reviewer;
+
+ RestReviewerSuggestion(final SuggestReviewerInfo reviewer) {
+ this.reviewer = reviewer;
+ }
+
+ public String getDisplayString() {
+ if (reviewer.account() != null) {
+ return FormatUtil.nameEmail(reviewer.account());
+ }
+ return reviewer.group().name()
+ + " ("
+ + Util.C.suggestedGroupLabel()
+ + ")";
+ }
+
+ public String getReplacementString() {
+ if (reviewer.account() != null) {
+ return FormatUtil.nameEmail(reviewer.account());
+ }
+ return reviewer.group().name();
+ }
+ }
+
+ public static class SuggestReviewerInfo extends JavaScriptObject {
+ public final native AccountInfo account() /*-{ return this.account; }-*/;
+ public final native GroupBaseInfo group() /*-{ return this.group; }-*/;
+ protected SuggestReviewerInfo() {
+ }
+ }
+}
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/Reviewers.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/Reviewers.java
new file mode 100644
index 0000000..b4e1f55
--- /dev/null
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/Reviewers.java
@@ -0,0 +1,225 @@
+// Copyright (C) 2013 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.ConfirmationCallback;
+import com.google.gerrit.client.ConfirmationDialog;
+import com.google.gerrit.client.Gerrit;
+import com.google.gerrit.client.account.AccountInfo;
+import com.google.gerrit.client.changes.ApprovalTable.PostInput;
+import com.google.gerrit.client.changes.ApprovalTable.PostResult;
+import com.google.gerrit.client.changes.ChangeApi;
+import com.google.gerrit.client.changes.ChangeInfo;
+import com.google.gerrit.client.changes.ChangeInfo.ApprovalInfo;
+import com.google.gerrit.client.changes.ChangeInfo.LabelInfo;
+import com.google.gerrit.client.changes.Util;
+import com.google.gerrit.client.rpc.GerritCallback;
+import com.google.gerrit.client.rpc.Natives;
+import com.google.gerrit.client.ui.HintTextBox;
+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.KeyCodes;
+import com.google.gwt.event.dom.client.KeyDownEvent;
+import com.google.gwt.event.dom.client.KeyDownHandler;
+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.Button;
+import com.google.gwt.user.client.ui.Composite;
+import com.google.gwt.user.client.ui.HTMLPanel;
+import com.google.gwt.user.client.ui.SuggestBox;
+import com.google.gwt.user.client.ui.SuggestBox.DefaultSuggestionDisplay;
+import com.google.gwt.user.client.ui.SuggestOracle.Suggestion;
+import com.google.gwt.user.client.ui.UIObject;
+import com.google.gwtexpui.safehtml.client.SafeHtml;
+import com.google.gwtexpui.safehtml.client.SafeHtmlBuilder;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/** Add reviewers. */
+class Reviewers extends Composite {
+ interface Binder extends UiBinder<HTMLPanel, Reviewers> {}
+ private static final Binder uiBinder = GWT.create(Binder.class);
+
+ @UiField Button openForm;
+ @UiField Element reviewers;
+ @UiField Element form;
+ @UiField Element error;
+ @UiField(provided = true)
+ SuggestBox suggestBox;
+
+ private RestReviewerSuggestOracle reviewerSuggestOracle;
+ private HintTextBox nameTxtBox;
+ private Change.Id changeId;
+ private ChangeScreen2.Style style;
+ private boolean submitOnSelection;
+
+ Reviewers() {
+ reviewerSuggestOracle = new RestReviewerSuggestOracle();
+ nameTxtBox = new HintTextBox();
+ suggestBox = new SuggestBox(reviewerSuggestOracle, nameTxtBox);
+ initWidget(uiBinder.createAndBindUi(this));
+
+ nameTxtBox.setVisibleLength(55);
+ nameTxtBox.setHintText(Util.C.approvalTableAddReviewerHint());
+ nameTxtBox.addKeyDownHandler(new KeyDownHandler() {
+ @Override
+ public void onKeyDown(KeyDownEvent e) {
+ submitOnSelection = false;
+
+ if (e.getNativeEvent().getKeyCode() == KeyCodes.KEY_ESCAPE) {
+ onCancel(null);
+ } else if (e.getNativeEvent().getKeyCode() == KeyCodes.KEY_ENTER) {
+ if (((DefaultSuggestionDisplay) suggestBox.getSuggestionDisplay())
+ .isSuggestionListShowing()) {
+ submitOnSelection = true;
+ } else {
+ onAdd(null);
+ }
+ }
+ }
+ });
+ suggestBox.addSelectionHandler(new SelectionHandler<Suggestion>() {
+ @Override
+ public void onSelection(SelectionEvent<Suggestion> event) {
+ nameTxtBox.setFocus(true);
+ if (submitOnSelection) {
+ onAdd(null);
+ }
+ }
+ });
+ }
+
+ void set(Change.Id changeId) {
+ this.changeId = changeId;
+ reviewerSuggestOracle.setChange(changeId);
+ }
+
+ void init(ChangeScreen2.Style style) {
+ this.style = style;
+ openForm.setVisible(Gerrit.isSignedIn());
+ }
+
+ void setReviewers(SafeHtml formatUserList) {
+ reviewers.setInnerSafeHtml(formatUserList);
+ }
+
+ @UiHandler("openForm")
+ void onOpenForm(ClickEvent e) {
+ onOpenForm();
+ }
+
+ void onOpenForm() {
+ UIObject.setVisible(form, true);
+ UIObject.setVisible(error, false);
+ openForm.setVisible(false);
+ suggestBox.setFocus(true);
+ }
+
+ @UiHandler("add")
+ void onAdd(ClickEvent e) {
+ String reviewer = suggestBox.getText();
+ if (!reviewer.isEmpty()) {
+ addReviewer(reviewer, false);
+ }
+ }
+
+ @UiHandler("cancel")
+ void onCancel(ClickEvent e) {
+ openForm.setVisible(true);
+ UIObject.setVisible(form, false);
+ suggestBox.setFocus(false);
+ }
+
+ private void addReviewer(final String reviewer, boolean confirmed) {
+ ChangeApi.reviewers(changeId.get()).post(
+ PostInput.create(reviewer, confirmed),
+ new GerritCallback<PostResult>() {
+ public void onSuccess(PostResult result) {
+ nameTxtBox.setEnabled(true);
+
+ if (result.confirm()) {
+ askForConfirmation(result.error());
+ } else if (result.error() != null) {
+ UIObject.setVisible(error, true);
+ error.setInnerText(result.error());
+ } else {
+ UIObject.setVisible(error, false);
+ error.setInnerText("");
+ nameTxtBox.setText("");
+
+ if (result.reviewers() != null
+ && result.reviewers().length() > 0) {
+ updateReviewerList();
+ }
+ }
+ }
+
+ private void askForConfirmation(String text) {
+ new ConfirmationDialog(
+ Util.C.approvalTableAddManyReviewersConfirmationDialogTitle(),
+ new SafeHtmlBuilder().append(text),
+ new ConfirmationCallback() {
+ @Override
+ public void onOk() {
+ addReviewer(reviewer, true);
+ }
+ }).center();
+ }
+
+ @Override
+ public void onFailure(Throwable err) {
+ UIObject.setVisible(error, true);
+ error.setInnerText(err instanceof StatusCodeException
+ ? ((StatusCodeException) err).getEncodedResponse()
+ : err.getMessage());
+ nameTxtBox.setEnabled(true);
+ }
+ });
+ }
+
+ private void updateReviewerList() {
+ ChangeApi.detail(changeId.get(),
+ new GerritCallback<ChangeInfo>() {
+ @Override
+ public void onSuccess(ChangeInfo result) {
+ display(result);
+ }
+ });
+ }
+
+ private void display(ChangeInfo info) {
+ Map<Integer, AccountInfo> r = new HashMap<Integer, AccountInfo>();
+ Map<Integer, AccountInfo> cc = new HashMap<Integer, AccountInfo>();
+ for (LabelInfo label : Natives.asList(info.all_labels().values())) {
+ if (label.all() != null) {
+ for (ApprovalInfo ai : Natives.asList(label.all())) {
+ (ai.value() != 0 ? r : cc).put(ai._account_id(), ai);
+ }
+ }
+ }
+ for (Integer i : r.keySet()) {
+ cc.remove(i);
+ }
+ cc.remove(info.owner()._account_id());
+ setReviewers(Labels.formatUserList(style, cc.values()));
+ }
+}
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/Reviewers.ui.xml b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/Reviewers.ui.xml
new file mode 100644
index 0000000..568e950
--- /dev/null
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/Reviewers.ui.xml
@@ -0,0 +1,73 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+Copyright (C) 2013 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'>
+ <ui:with field='res' type='com.google.gerrit.client.change.Resources'/>
+ <ui:style>
+ .openAdd {
+ cursor: pointer;
+ float: right;
+ padding: 0;
+ margin: 0;
+ border: 0;
+ background-color: transparent;
+ }
+
+ .suggestBox {
+ margin-bottom: 2px;
+ }
+
+ .error {
+ color: #D33D3D;
+ font-weight: bold;
+ }
+
+ .cancel {
+ float: right;
+ }
+ </ui:style>
+ <g:HTMLPanel>
+ <div>
+ <span ui:field='reviewers'/>
+ <g:Button ui:field='openForm'
+ title='Add reviewers to this change'
+ styleName='{style.openAdd}'
+ visible='false'>
+ <ui:attribute name='title'/>
+ <div>[+]</div>
+ </g:Button>
+ </div>
+ <div ui:field='form' style='display: none' aria-hidden='true'>
+ <g:SuggestBox ui:field='suggestBox' styleName='{style.suggestBox}'/>
+ <div ui:field='error'
+ class='{style.error}'
+ style='display: none' aria-hidden='true'/>
+ <div>
+ <g:Button ui:field='add' styleName='{res.style.button}'>
+ <div>Add</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/changes/ApprovalTable.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ApprovalTable.java
index 93c9505..350f97f 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ApprovalTable.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ApprovalTable.java
@@ -254,8 +254,8 @@
}
}
- private static class PostInput extends JavaScriptObject {
- static PostInput create(String reviewer, boolean confirmed) {
+ public static class PostInput extends JavaScriptObject {
+ public static PostInput create(String reviewer, boolean confirmed) {
PostInput input = createObject().cast();
input.init(reviewer, confirmed);
return input;
@@ -272,7 +272,7 @@
}
}
- private static class ReviewerInfo extends AccountInfo {
+ public static class ReviewerInfo extends AccountInfo {
final Set<String> approvals() {
return Natives.keys(_approvals());
}
@@ -283,10 +283,10 @@
}
}
- private static class PostResult extends JavaScriptObject {
- final native JsArray<ReviewerInfo> reviewers() /*-{ return this.reviewers; }-*/;
- final native boolean confirm() /*-{ return this.confirm || false; }-*/;
- final native String error() /*-{ return this.error; }-*/;
+ public static class PostResult extends JavaScriptObject {
+ public final native JsArray<ReviewerInfo> reviewers() /*-{ return this.reviewers; }-*/;
+ public final native boolean confirm() /*-{ return this.confirm || false; }-*/;
+ public final native String error() /*-{ return this.error; }-*/;
protected PostResult() {
}
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 c292154..9d76104 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
@@ -86,6 +86,12 @@
return change(id).view("reviewers");
}
+ public static RestApi suggestReviewers(int id, String q, int n) {
+ return change(id).view("suggest_reviewers")
+ .addParameter("q", q)
+ .addParameter("n", n);
+ }
+
public static RestApi reviewer(int id, int reviewer) {
return change(id).view("reviewers").id(reviewer);
}
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 197b466..81ab944 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
@@ -61,6 +61,7 @@
String keyPublishComments();
String keyEditTopic();
String keyEditMessage();
+ String keyAddReviewers();
String patchTableColumnName();
String patchTableColumnComments();
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 f51cff0..86623dc 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
@@ -41,6 +41,7 @@
keyPublishComments = Review and publish comments
keyEditTopic = Edit change topic
keyEditMessage = Edit commit message
+keyAddReviewers = Add reviewers
patchTableColumnName = File Path