blob: 859af19b4cb7d14357d446db3c1dce268cd8b248 [file] [log] [blame]
// 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.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.info.ChangeInfo.ApprovalInfo;
import com.google.gerrit.client.info.ChangeInfo.LabelInfo;
import com.google.gerrit.client.rpc.GerritCallback;
import com.google.gerrit.client.rpc.NativeMap;
import com.google.gerrit.client.rpc.NativeString;
import com.google.gerrit.client.rpc.Natives;
import com.google.gerrit.client.ui.RemoteSuggestBox;
import com.google.gerrit.extensions.client.ReviewerState;
import com.google.gerrit.reviewdb.client.Change;
import com.google.gerrit.reviewdb.client.Project;
import com.google.gwt.core.client.GWT;
import com.google.gwt.core.client.JavaScriptObject;
import com.google.gwt.core.client.JsArray;
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.Button;
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;
import com.google.gwtexpui.safehtml.client.SafeHtml;
import com.google.gwtexpui.safehtml.client.SafeHtmlBuilder;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
/** Add reviewers. */
public class Reviewers extends Composite {
interface Binder extends UiBinder<HTMLPanel, Reviewers> {}
private static final Binder uiBinder = GWT.create(Binder.class);
@UiField Element reviewersText;
@UiField Image addReviewerIcon;
@UiField Button addMe;
@UiField Element form;
@UiField Element error;
@UiField(provided = true)
RemoteSuggestBox suggestBox;
private ChangeScreen.Style style;
private Element ccText;
private ReviewerSuggestOracle reviewerSuggestOracle;
private Change.Id changeId;
private Project.NameKey project;
Reviewers() {
reviewerSuggestOracle = new ReviewerSuggestOracle();
suggestBox = new RemoteSuggestBox(reviewerSuggestOracle);
suggestBox.enableDefaultSuggestions();
suggestBox.setVisibleLength(55);
suggestBox.setHintText(Util.C.approvalTableAddReviewerHint());
suggestBox.addCloseHandler(
new CloseHandler<RemoteSuggestBox>() {
@Override
public void onClose(CloseEvent<RemoteSuggestBox> event) {
Reviewers.this.onCancel(null);
}
});
suggestBox.addSelectionHandler(
new SelectionHandler<String>() {
@Override
public void onSelection(SelectionEvent<String> event) {
addReviewer(event.getSelectedItem(), false);
}
});
initWidget(uiBinder.createAndBindUi(this));
addReviewerIcon.addDomHandler(
new ClickHandler() {
@Override
public void onClick(ClickEvent event) {
onOpenForm();
}
},
ClickEvent.getType());
}
void init(ChangeScreen.Style style, Element ccText) {
this.style = style;
this.ccText = ccText;
}
void set(ChangeInfo info) {
this.changeId = info.legacyId();
this.project = info.projectNameKey();
display(info);
reviewerSuggestOracle.setChange(project, changeId);
addReviewerIcon.setVisible(Gerrit.isSignedIn());
}
void onOpenForm() {
UIObject.setVisible(form, true);
UIObject.setVisible(error, false);
addReviewerIcon.setVisible(false);
suggestBox.setServeSuggestionsOnOracle(true);
suggestBox.setFocus(true);
}
@UiHandler("add")
void onAdd(@SuppressWarnings("unused") ClickEvent e) {
addReviewer(suggestBox.getText(), false);
}
@UiHandler("addMe")
void onAddMe(@SuppressWarnings("unused") ClickEvent e) {
String accountId = String.valueOf(Gerrit.getUserAccount()._accountId());
addReviewer(accountId, false);
}
@UiHandler("cancel")
void onCancel(@SuppressWarnings("unused") ClickEvent e) {
addReviewerIcon.setVisible(true);
UIObject.setVisible(form, false);
suggestBox.setFocus(false);
suggestBox.setText("");
suggestBox.setServeSuggestionsOnOracle(false);
}
private void addReviewer(String reviewer, boolean confirmed) {
if (reviewer.isEmpty()) {
return;
}
ChangeApi.reviewers(project.get(), changeId.get())
.post(
PostInput.create(reviewer, confirmed),
new GerritCallback<PostResult>() {
@Override
public void onSuccess(PostResult result) {
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("");
suggestBox.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) {
if (isSigninFailure(err)) {
new NotSignedInDialog().center();
} else {
UIObject.setVisible(error, true);
error.setInnerText(
err instanceof StatusCodeException
? ((StatusCodeException) err).getEncodedResponse()
: err.getMessage());
}
}
});
}
void updateReviewerList() {
ChangeApi.detail(
project.get(),
changeId.get(),
new GerritCallback<ChangeInfo>() {
@Override
public void onSuccess(ChangeInfo result) {
display(result);
}
});
}
private void display(ChangeInfo info) {
Map<ReviewerState, List<AccountInfo>> reviewers = info.reviewers();
Map<Integer, AccountInfo> r = byAccount(reviewers, ReviewerState.REVIEWER);
Map<Integer, AccountInfo> cc = byAccount(reviewers, ReviewerState.CC);
for (Integer i : r.keySet()) {
cc.remove(i);
}
cc.remove(info.owner()._accountId());
Set<Integer> removable = info.removableReviewerIds();
Map<Integer, VotableInfo> votable = votable(info);
SafeHtml rHtml = Labels.formatUserList(style, r.values(), removable, null, votable);
SafeHtml ccHtml = Labels.formatUserList(style, cc.values(), removable, null, votable);
reviewersText.setInnerSafeHtml(rHtml);
ccText.setInnerSafeHtml(ccHtml);
if (Gerrit.isSignedIn()) {
int currentUser = Gerrit.getUserAccount()._accountId();
boolean showAddMeButton =
info.owner()._accountId() != currentUser
&& !cc.containsKey(currentUser)
&& !r.containsKey(currentUser);
addMe.setVisible(showAddMeButton);
}
}
private static Map<Integer, AccountInfo> byAccount(
Map<ReviewerState, List<AccountInfo>> reviewers, ReviewerState state) {
List<AccountInfo> accounts = reviewers.get(state);
if (accounts == null) {
return Collections.emptyMap();
}
Map<Integer, AccountInfo> result = new HashMap<>();
for (AccountInfo a : accounts) {
result.put(a._accountId(), a);
}
return result;
}
private static Map<Integer, VotableInfo> votable(ChangeInfo change) {
Map<Integer, VotableInfo> d = new HashMap<>();
for (String name : change.labels()) {
LabelInfo label = change.label(name);
Short labelMaxValue =
label.valueSet().isEmpty() ? null : LabelInfo.parseValue(label.maxValue());
if (label.all() != null) {
for (ApprovalInfo ai : Natives.asList(label.all())) {
int id = ai._accountId();
VotableInfo ad = d.get(id);
if (ad == null) {
ad = new VotableInfo();
d.put(id, ad);
}
if (labelMaxValue != null
&& ai.permittedVotingRange() != null
&& ai.permittedVotingRange().max() == labelMaxValue) {
ad.votable(name + " (" + label.maxValue() + ") ");
} else if (ai.hasValue()) {
ad.votable(name);
}
}
}
}
return d;
}
public static class PostInput extends JavaScriptObject {
public static PostInput create(String reviewer, boolean confirmed) {
PostInput input = createObject().cast();
input.init(reviewer, confirmed);
return input;
}
private native void init(String reviewer, boolean confirmed) /*-{
this.reviewer = reviewer;
if (confirmed) {
this.confirmed = true;
}
}-*/;
protected PostInput() {}
}
public static class ReviewerInfo extends AccountInfo {
final Set<String> approvals() {
return Natives.keys(_approvals());
}
final native String approval(String l) /*-{ return this.approvals[l]; }-*/;
private native NativeMap<NativeString> _approvals() /*-{ return this.approvals; }-*/;
protected ReviewerInfo() {}
}
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() {}
}
}