| // Copyright (C) 2008 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.changes; |
| |
| import com.google.gerrit.client.ConfirmationCallback; |
| import com.google.gerrit.client.ConfirmationDialog; |
| import com.google.gerrit.client.ErrorDialog; |
| import com.google.gerrit.client.FormatUtil; |
| import com.google.gerrit.client.Gerrit; |
| import com.google.gerrit.client.patches.PatchUtil; |
| import com.google.gerrit.client.rpc.GerritCallback; |
| import com.google.gerrit.client.ui.AccountLink; |
| import com.google.gerrit.client.ui.AddMemberBox; |
| import com.google.gerrit.client.ui.ReviewerSuggestOracle; |
| import com.google.gerrit.common.data.AccountInfoCache; |
| import com.google.gerrit.common.data.ApprovalDetail; |
| import com.google.gerrit.common.data.ApprovalType; |
| import com.google.gerrit.common.data.ApprovalTypes; |
| import com.google.gerrit.common.data.ChangeDetail; |
| import com.google.gerrit.common.data.PatchSetPublishDetail; |
| import com.google.gerrit.common.data.ReviewerResult; |
| import com.google.gerrit.common.data.SubmitRecord; |
| import com.google.gerrit.reviewdb.client.Account; |
| import com.google.gerrit.reviewdb.client.Change; |
| import com.google.gerrit.reviewdb.client.PatchSetApproval; |
| import com.google.gwt.event.dom.client.ClickEvent; |
| import com.google.gwt.event.dom.client.ClickHandler; |
| import com.google.gwt.user.client.DOM; |
| import com.google.gwt.user.client.Element; |
| import com.google.gwt.user.client.ui.Composite; |
| import com.google.gwt.user.client.ui.FlowPanel; |
| import com.google.gwt.user.client.ui.Grid; |
| import com.google.gwt.user.client.ui.HTMLTable.CellFormatter; |
| import com.google.gwt.user.client.ui.Image; |
| import com.google.gwt.user.client.ui.Panel; |
| import com.google.gwt.user.client.ui.PushButton; |
| import com.google.gwt.user.client.ui.Widget; |
| import com.google.gwtexpui.safehtml.client.SafeHtmlBuilder; |
| |
| import java.util.ArrayList; |
| import java.util.Collections; |
| import java.util.Comparator; |
| import java.util.HashMap; |
| import java.util.HashSet; |
| import java.util.List; |
| |
| /** Displays a table of {@link ApprovalDetail} objects for a change record. */ |
| public class ApprovalTable extends Composite { |
| private final ApprovalTypes types; |
| private final Grid table; |
| private final Widget missing; |
| private final Panel addReviewer; |
| private final ReviewerSuggestOracle reviewerSuggestOracle; |
| private final AddMemberBox addMemberBox; |
| private Change.Id changeId; |
| private AccountInfoCache accountCache = AccountInfoCache.empty(); |
| |
| public ApprovalTable() { |
| types = Gerrit.getConfig().getApprovalTypes(); |
| table = new Grid(1, 3); |
| table.addStyleName(Gerrit.RESOURCES.css().infoTable()); |
| |
| missing = new Widget() { |
| { |
| setElement(DOM.createElement("ul")); |
| } |
| }; |
| missing.setStyleName(Gerrit.RESOURCES.css().missingApprovalList()); |
| |
| addReviewer = new FlowPanel(); |
| addReviewer.setStyleName(Gerrit.RESOURCES.css().addReviewer()); |
| reviewerSuggestOracle = new ReviewerSuggestOracle(); |
| addMemberBox = |
| new AddMemberBox(Util.C.approvalTableAddReviewer(), |
| Util.C.approvalTableAddReviewerHint(), reviewerSuggestOracle); |
| addMemberBox.addClickHandler(new ClickHandler() { |
| @Override |
| public void onClick(final ClickEvent event) { |
| doAddReviewer(); |
| } |
| }); |
| addReviewer.add(addMemberBox); |
| addReviewer.setVisible(false); |
| |
| final FlowPanel fp = new FlowPanel(); |
| fp.add(table); |
| fp.add(missing); |
| fp.add(addReviewer); |
| initWidget(fp); |
| |
| setStyleName(Gerrit.RESOURCES.css().approvalTable()); |
| } |
| |
| private void displayHeader(List<String> labels) { |
| table.resizeColumns(2 + labels.size()); |
| |
| final CellFormatter fmt = table.getCellFormatter(); |
| int col = 0; |
| |
| table.setText(0, col, Util.C.approvalTableReviewer()); |
| fmt.setStyleName(0, col, Gerrit.RESOURCES.css().header()); |
| col++; |
| |
| table.clearCell(0, col); |
| fmt.setStyleName(0, col, Gerrit.RESOURCES.css().header()); |
| col++; |
| |
| for (String name : labels) { |
| table.setText(0, col, name); |
| fmt.setStyleName(0, col, Gerrit.RESOURCES.css().header()); |
| col++; |
| } |
| fmt.addStyleName(0, col - 1, Gerrit.RESOURCES.css().rightmost()); |
| } |
| |
| public void setAccountInfoCache(final AccountInfoCache aic) { |
| assert aic != null; |
| accountCache = aic; |
| } |
| |
| private AccountLink link(final Account.Id id) { |
| return AccountLink.link(accountCache, id); |
| } |
| |
| void display(PatchSetPublishDetail detail) { |
| doDisplay(detail.getChange(), detail.getApprovals(), |
| detail.getSubmitRecords()); |
| } |
| |
| void display(ChangeDetail detail) { |
| doDisplay(detail.getChange(), detail.getApprovals(), |
| detail.getSubmitRecords()); |
| } |
| |
| private void doDisplay(Change change, List<ApprovalDetail> approvals, |
| List<SubmitRecord> submitRecords) { |
| changeId = change.getId(); |
| reviewerSuggestOracle.setChange(changeId); |
| |
| List<String> columns = new ArrayList<String>(); |
| |
| final Element missingList = missing.getElement(); |
| while (DOM.getChildCount(missingList) > 0) { |
| DOM.removeChild(missingList, DOM.getChild(missingList, 0)); |
| } |
| missing.setVisible(false); |
| |
| if (submitRecords != null) { |
| HashSet<String> reportedMissing = new HashSet<String>(); |
| |
| HashMap<Account.Id, ApprovalDetail> byUser = |
| new HashMap<Account.Id, ApprovalDetail>(); |
| for (ApprovalDetail ad : approvals) { |
| byUser.put(ad.getAccount(), ad); |
| } |
| |
| for (SubmitRecord rec : submitRecords) { |
| if (rec.labels == null) { |
| continue; |
| } |
| |
| for (SubmitRecord.Label lbl : rec.labels) { |
| if (!columns.contains(lbl.label)) { |
| columns.add(lbl.label); |
| } |
| |
| switch (lbl.status) { |
| case OK: { |
| ApprovalDetail ad = byUser.get(lbl.appliedBy); |
| if (ad != null) { |
| ad.approved(lbl.label); |
| } |
| break; |
| } |
| |
| case REJECT: { |
| ApprovalDetail ad = byUser.get(lbl.appliedBy); |
| if (ad != null) { |
| ad.rejected(lbl.label); |
| } |
| break; |
| } |
| |
| case MAY: |
| break; |
| |
| case NEED: |
| case IMPOSSIBLE: |
| if (reportedMissing.add(lbl.label)) { |
| Element li = DOM.createElement("li"); |
| li.setClassName(Gerrit.RESOURCES.css().missingApproval()); |
| DOM.setInnerText(li, Util.M.needApproval(lbl.label)); |
| DOM.appendChild(missingList, li); |
| } |
| break; |
| } |
| } |
| } |
| missing.setVisible(!reportedMissing.isEmpty()); |
| |
| } else { |
| for (ApprovalDetail ad : approvals) { |
| for (PatchSetApproval psa : ad.getPatchSetApprovals()) { |
| ApprovalType legacyType = types.byId(psa.getCategoryId()); |
| if (legacyType == null) { |
| continue; |
| } |
| String labelName = legacyType.getCategory().getLabelName(); |
| if (psa.getValue() != 0 ) { |
| if (psa.getValue() == legacyType.getMax().getValue()) { |
| ad.approved(labelName); |
| } else if (psa.getValue() == legacyType.getMin().getValue()) { |
| ad.rejected(labelName); |
| } |
| } |
| if (!columns.contains(labelName)) { |
| columns.add(labelName); |
| } |
| } |
| Collections.sort(columns, new Comparator<String>() { |
| @Override |
| public int compare(String o1, String o2) { |
| ApprovalType a = types.byLabel(o1); |
| ApprovalType b = types.byLabel(o2); |
| int cmp = 0; |
| if (a != null && b != null) { |
| cmp = a.getCategory().getPosition() - b.getCategory().getPosition(); |
| } |
| if (cmp == 0) { |
| cmp = o1.compareTo(o2); |
| } |
| return cmp; |
| } |
| }); |
| } |
| } |
| |
| if (approvals.isEmpty()) { |
| table.setVisible(false); |
| } else { |
| displayHeader(columns); |
| table.resizeRows(1 + approvals.size()); |
| for (int i = 0; i < approvals.size(); i++) { |
| displayRow(i + 1, approvals.get(i), change, columns); |
| } |
| table.setVisible(true); |
| } |
| |
| addReviewer.setVisible(Gerrit.isSignedIn()); |
| |
| if (Gerrit.getConfig().testChangeMerge() |
| && !change.isMergeable()) { |
| Element li = DOM.createElement("li"); |
| li.setClassName(Gerrit.RESOURCES.css().missingApproval()); |
| DOM.setInnerText(li, Util.C.messageNeedsRebaseOrHasDependency()); |
| DOM.appendChild(missingList, li); |
| missing.setVisible(true); |
| } |
| } |
| |
| private void doAddReviewer() { |
| final String reviewer = addMemberBox.getText(); |
| if (reviewer.length() == 0) { |
| return; |
| } |
| |
| addMemberBox.setEnabled(false); |
| final List<String> reviewers = new ArrayList<String>(); |
| reviewers.add(reviewer); |
| |
| addReviewers(reviewers, false); |
| } |
| |
| private void addReviewers(final List<String> reviewers, |
| final boolean confirmed) { |
| PatchUtil.DETAIL_SVC.addReviewers(changeId, reviewers, confirmed, |
| new GerritCallback<ReviewerResult>() { |
| public void onSuccess(final ReviewerResult result) { |
| addMemberBox.setEnabled(true); |
| addMemberBox.setText(""); |
| |
| final ChangeDetail changeDetail = result.getChange(); |
| if (changeDetail != null) { |
| setAccountInfoCache(changeDetail.getAccounts()); |
| display(changeDetail); |
| } |
| |
| if (!result.getErrors().isEmpty()) { |
| final SafeHtmlBuilder r = new SafeHtmlBuilder(); |
| for (final ReviewerResult.Error e : result.getErrors()) { |
| switch (e.getType()) { |
| case REVIEWER_NOT_FOUND: |
| r.append(Util.M.reviewerNotFound(e.getName())); |
| break; |
| |
| case ACCOUNT_INACTIVE: |
| r.append(Util.M.accountInactive(e.getName())); |
| break; |
| |
| case CHANGE_NOT_VISIBLE: |
| r.append(Util.M.changeNotVisibleTo(e.getName())); |
| break; |
| |
| case GROUP_EMPTY: |
| r.append(Util.M.groupIsEmpty(e.getName())); |
| break; |
| |
| case GROUP_HAS_TOO_MANY_MEMBERS: |
| if (result.askForConfirmation() && !confirmed) { |
| askForConfirmation(e.getName(), result.getMemberCount()); |
| return; |
| } else { |
| r.append(Util.M.groupHasTooManyMembers(e.getName())); |
| } |
| break; |
| |
| case GROUP_NOT_ALLOWED: |
| r.append(Util.M.groupIsNotAllowed(e.getName())); |
| break; |
| |
| default: |
| r.append(e.getName()); |
| r.append(" - "); |
| r.append(e.getType()); |
| r.br(); |
| break; |
| } |
| } |
| new ErrorDialog(r).center(); |
| } |
| } |
| |
| private void askForConfirmation(final String groupName, |
| final int memberCount) { |
| final SafeHtmlBuilder b = new SafeHtmlBuilder(); |
| b.openElement("b"); |
| b.append(Util.M |
| .groupManyMembersConfirmation(groupName, memberCount)); |
| b.closeElement("b"); |
| final ConfirmationDialog confirmationDialog = |
| new ConfirmationDialog(Util.C |
| .approvalTableAddManyReviewersConfirmationDialogTitle(), |
| b.toSafeHtml(), new ConfirmationCallback() { |
| @Override |
| public void onOk() { |
| addReviewers(reviewers, true); |
| } |
| }); |
| confirmationDialog.center(); |
| } |
| |
| @Override |
| public void onFailure(final Throwable caught) { |
| addMemberBox.setEnabled(true); |
| super.onFailure(caught); |
| } |
| }); |
| } |
| |
| private void displayRow(final int row, final ApprovalDetail ad, |
| final Change change, List<String> columns) { |
| final CellFormatter fmt = table.getCellFormatter(); |
| int col = 0; |
| |
| table.setWidget(row, col++, link(ad.getAccount())); |
| |
| if (ad.canRemove()) { |
| final PushButton remove = new PushButton( // |
| new Image(Util.R.removeReviewerNormal()), // |
| new Image(Util.R.removeReviewerPressed())); |
| remove.setTitle(Util.M.removeReviewer( // |
| FormatUtil.name(accountCache.get(ad.getAccount())))); |
| remove.setStyleName(Gerrit.RESOURCES.css().removeReviewer()); |
| remove.addClickHandler(new ClickHandler() { |
| @Override |
| public void onClick(ClickEvent event) { |
| doRemove(ad, remove); |
| } |
| }); |
| table.setWidget(row, col, remove); |
| } else { |
| table.clearCell(row, col); |
| } |
| fmt.setStyleName(row, col++, Gerrit.RESOURCES.css().removeReviewerCell()); |
| |
| for (String labelName : columns) { |
| fmt.setStyleName(row, col, Gerrit.RESOURCES.css().approvalscore()); |
| |
| if (ad.isRejected(labelName)) { |
| table.setWidget(row, col, new Image(Gerrit.RESOURCES.redNot())); |
| |
| } else if (ad.isApproved(labelName)) { |
| table.setWidget(row, col, new Image(Gerrit.RESOURCES.greenCheck())); |
| |
| } else { |
| ApprovalType legacyType = types.byLabel(labelName); |
| if (legacyType == null) { |
| table.clearCell(row, col); |
| col++; |
| continue; |
| } |
| |
| PatchSetApproval ca = ad.getPatchSetApproval(legacyType.getCategory().getId()); |
| if (ca == null || ca.getValue() == 0) { |
| table.clearCell(row, col); |
| col++; |
| continue; |
| } |
| |
| String vstr = String.valueOf(ca.getValue()); |
| if (ca.getValue() > 0) { |
| vstr = "+" + vstr; |
| fmt.addStyleName(row, col, Gerrit.RESOURCES.css().posscore()); |
| } else { |
| fmt.addStyleName(row, col, Gerrit.RESOURCES.css().negscore()); |
| } |
| table.setText(row, col, vstr); |
| } |
| |
| col++; |
| } |
| |
| fmt.addStyleName(row, col - 1, Gerrit.RESOURCES.css().rightmost()); |
| } |
| |
| private void doRemove(final ApprovalDetail ad, final PushButton remove) { |
| remove.setEnabled(false); |
| PatchUtil.DETAIL_SVC.removeReviewer(changeId, ad.getAccount(), |
| new GerritCallback<ReviewerResult>() { |
| @Override |
| public void onSuccess(ReviewerResult result) { |
| if (result.getErrors().isEmpty()) { |
| final ChangeDetail r = result.getChange(); |
| display(r); |
| } else { |
| final ReviewerResult.Error resultError = |
| result.getErrors().get(0); |
| String message; |
| switch (resultError.getType()) { |
| case REMOVE_NOT_PERMITTED: |
| message = Util.C.approvalTableRemoveNotPermitted(); |
| break; |
| case COULD_NOT_REMOVE: |
| default: |
| message = Util.C.approvalTableCouldNotRemove(); |
| } |
| new ErrorDialog(message + " " + resultError.getName()).center(); |
| } |
| } |
| |
| @Override |
| public void onFailure(final Throwable caught) { |
| remove.setEnabled(true); |
| super.onFailure(caught); |
| } |
| }); |
| } |
| } |