| // 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() {} |
| } |
| } |