ChangeScreen2: Support adding reviewers
Bug: issue 2066 Change-Id: I3654d019ce09c1b2c234c0b73f5d6c0e6c8ae0b9
This commit is contained in:
committed by
Shawn Pearce
parent
0775a800e3
commit
a19be89e15
@@ -120,7 +120,7 @@ public class ChangeScreen2 extends Screen {
|
||||
@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 @@ public class ChangeScreen2 extends Screen {
|
||||
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 @@ public class ChangeScreen2 extends Screen {
|
||||
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 @@ public class ChangeScreen2 extends Screen {
|
||||
}
|
||||
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) {
|
||||
|
||||
@@ -281,7 +281,9 @@ limitations under the License.
|
||||
</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>
|
||||
|
||||
@@ -123,7 +123,7 @@ class Labels extends Grid {
|
||||
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 @@ class Labels extends Grid {
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
|
||||
@@ -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() {
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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()));
|
||||
}
|
||||
}
|
||||
@@ -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>
|
||||
@@ -254,8 +254,8 @@ public class ApprovalTable extends Composite {
|
||||
}
|
||||
}
|
||||
|
||||
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 @@ public class ApprovalTable extends Composite {
|
||||
}
|
||||
}
|
||||
|
||||
private static class ReviewerInfo extends AccountInfo {
|
||||
public static class ReviewerInfo extends AccountInfo {
|
||||
final Set<String> approvals() {
|
||||
return Natives.keys(_approvals());
|
||||
}
|
||||
@@ -283,10 +283,10 @@ public class ApprovalTable extends Composite {
|
||||
}
|
||||
}
|
||||
|
||||
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() {
|
||||
}
|
||||
|
||||
@@ -86,6 +86,12 @@ public class ChangeApi {
|
||||
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);
|
||||
}
|
||||
|
||||
@@ -61,6 +61,7 @@ public interface ChangeConstants extends Constants {
|
||||
String keyPublishComments();
|
||||
String keyEditTopic();
|
||||
String keyEditMessage();
|
||||
String keyAddReviewers();
|
||||
|
||||
String patchTableColumnName();
|
||||
String patchTableColumnComments();
|
||||
|
||||
@@ -41,6 +41,7 @@ keyReloadSearch = Reload change list
|
||||
keyPublishComments = Review and publish comments
|
||||
keyEditTopic = Edit change topic
|
||||
keyEditMessage = Edit commit message
|
||||
keyAddReviewers = Add reviewers
|
||||
|
||||
|
||||
patchTableColumnName = File Path
|
||||
|
||||
Reference in New Issue
Block a user