blob: 267a983ebcaec7ecd630d6c6fe21e56cfa7ec52c [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 static java.util.stream.Collectors.collectingAndThen;
import static java.util.stream.Collectors.toCollection;
import static java.util.stream.Collectors.toList;
import com.google.gerrit.client.Gerrit;
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.AccountInfo.AvatarInfo;
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.Natives;
import com.google.gerrit.common.PageLinks;
import com.google.gerrit.common.data.LabelValue;
import com.google.gerrit.reviewdb.client.Change;
import com.google.gwt.core.client.JavaScriptObject;
import com.google.gwt.dom.client.Element;
import com.google.gwt.dom.client.NativeEvent;
import com.google.gwt.user.client.DOM;
import com.google.gwt.user.client.ui.Grid;
import com.google.gwt.user.client.ui.Widget;
import com.google.gwtexpui.safehtml.client.SafeHtml;
import com.google.gwtexpui.safehtml.client.SafeHtmlBuilder;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
/** Displays a table of label and reviewer scores. */
class Labels extends Grid {
private static final String DATA_ID = "data-id";
private static final String DATA_VOTE = "data-vote";
private static final String REMOVE_REVIEWER;
private static final String REMOVE_VOTE;
static {
REMOVE_REVIEWER = DOM.createUniqueId().replace('-', '_');
REMOVE_VOTE = DOM.createUniqueId().replace('-', '_');
init(REMOVE_REVIEWER, REMOVE_VOTE);
}
private static native void init(String r, String v) /*-{
$wnd[r] = $entry(function(e) {
@com.google.gerrit.client.change.Labels::onRemoveReviewer(Lcom/google/gwt/dom/client/NativeEvent;)(e)
});
$wnd[v] = $entry(function(e) {
@com.google.gerrit.client.change.Labels::onRemoveVote(Lcom/google/gwt/dom/client/NativeEvent;)(e)
});
}-*/;
private static void onRemoveReviewer(NativeEvent event) {
Integer user = getDataId(event);
if (user != null) {
final ChangeScreen screen = ChangeScreen.get(event);
final Change.Id changeId = screen.getPatchSetId().getParentKey();
ChangeApi.reviewer(screen.getProject().get(), changeId.get(), user)
.delete(
new GerritCallback<JavaScriptObject>() {
@Override
public void onSuccess(JavaScriptObject result) {
if (screen.isCurrentView()) {
Gerrit.display(PageLinks.toChange(screen.getProject(), changeId));
}
}
});
}
}
private static void onRemoveVote(NativeEvent event) {
Integer user = getDataId(event);
String vote = getVoteId(event);
if (user != null && vote != null) {
final ChangeScreen screen = ChangeScreen.get(event);
final Change.Id changeId = screen.getPatchSetId().getParentKey();
ChangeApi.vote(screen.getProject().get(), changeId.get(), user, vote)
.delete(
new GerritCallback<JavaScriptObject>() {
@Override
public void onSuccess(JavaScriptObject result) {
if (screen.isCurrentView()) {
Gerrit.display(PageLinks.toChange(screen.getProject(), changeId));
}
}
});
}
}
private static Integer getDataId(NativeEvent event) {
Element e = event.getEventTarget().cast();
while (e != null) {
String v = e.getAttribute(DATA_ID);
if (!v.isEmpty()) {
return Integer.parseInt(v);
}
e = e.getParentElement();
}
return null;
}
private static String getVoteId(NativeEvent event) {
Element e = event.getEventTarget().cast();
while (e != null) {
String v = e.getAttribute(DATA_VOTE);
if (!v.isEmpty()) {
return v;
}
e = e.getParentElement();
}
return null;
}
private ChangeScreen.Style style;
void init(ChangeScreen.Style style) {
this.style = style;
}
void set(ChangeInfo info) {
List<String> names =
info.labels().stream()
.sorted()
.collect(collectingAndThen(toList(), Collections::unmodifiableList));
Set<Integer> removable = info.removableReviewerIds();
resize(names.size(), 2);
for (int row = 0; row < names.size(); row++) {
String name = names.get(row);
LabelInfo label = info.label(name);
setText(row, 0, name);
if (label.all() != null) {
setWidget(row, 1, renderUsers(label, removable));
}
getCellFormatter().setStyleName(row, 0, style.labelName());
getCellFormatter().addStyleName(row, 0, getStyleForLabel(label));
}
}
private Widget renderUsers(LabelInfo label, Set<Integer> removable) {
Map<Integer, List<ApprovalInfo>> m = new HashMap<>(4);
int approved = 0;
int rejected = 0;
for (ApprovalInfo ai : Natives.asList(label.all())) {
if (ai.value() != 0) {
List<ApprovalInfo> l = m.get(Integer.valueOf(ai.value()));
if (l == null) {
l = new ArrayList<>(label.all().length());
m.put(Integer.valueOf(ai.value()), l);
}
l.add(ai);
if (isRejected(label, ai)) {
rejected = ai.value();
} else if (isApproved(label, ai)) {
approved = ai.value();
}
}
}
SafeHtmlBuilder html = new SafeHtmlBuilder();
for (Integer v : sort(m.keySet(), approved, rejected)) {
if (!html.isEmpty()) {
html.br();
}
String val = LabelValue.formatValue(v.shortValue());
html.openSpan();
html.setAttribute("title", label.valueText(val));
if (v.intValue() == approved) {
html.setStyleName(style.label_ok());
} else if (v.intValue() == rejected) {
html.setStyleName(style.label_reject());
}
html.append(val).append(" ");
html.append(formatUserList(style, m.get(v), removable, label.name(), null));
html.closeSpan();
}
return html.toBlockWidget();
}
private static List<Integer> sort(Set<Integer> keySet, int a, int b) {
List<Integer> r = keySet.stream().sorted().collect(toCollection(ArrayList::new));
if (keySet.contains(a)) {
r.remove(Integer.valueOf(a));
r.add(0, a);
} else if (keySet.contains(b)) {
r.remove(Integer.valueOf(b));
r.add(0, b);
}
return r;
}
private static boolean isApproved(LabelInfo label, ApprovalInfo ai) {
return label.approved() != null && label.approved()._accountId() == ai._accountId();
}
private static boolean isRejected(LabelInfo label, ApprovalInfo ai) {
return label.rejected() != null && label.rejected()._accountId() == ai._accountId();
}
private String getStyleForLabel(LabelInfo label) {
switch (label.status()) {
case OK:
return style.label_ok();
case NEED:
return style.label_need();
case REJECT:
case IMPOSSIBLE:
return style.label_reject();
default:
case MAY:
return style.label_may();
}
}
static SafeHtml formatUserList(
ChangeScreen.Style style,
Collection<? extends AccountInfo> in,
Set<Integer> removable,
String label,
Map<Integer, VotableInfo> votable) {
List<AccountInfo> users =
in.stream()
.sorted(
new Comparator<AccountInfo>() {
@Override
public int compare(AccountInfo a, AccountInfo b) {
String as = name(a);
String bs = name(b);
if (as.isEmpty()) {
return 1;
} else if (bs.isEmpty()) {
return -1;
}
return as.compareTo(bs);
}
private String name(AccountInfo a) {
if (a.name() != null) {
return a.name();
} else if (a.email() != null) {
return a.email();
}
return "";
}
})
.collect(collectingAndThen(toList(), Collections::unmodifiableList));
SafeHtmlBuilder html = new SafeHtmlBuilder();
Iterator<? extends AccountInfo> itr = users.iterator();
while (itr.hasNext()) {
AccountInfo ai = itr.next();
AvatarInfo img = ai.avatar(AvatarInfo.DEFAULT_SIZE);
String name;
if (ai.name() != null) {
name = ai.name();
} else if (ai.email() != null) {
name = ai.email();
} else {
name = Integer.toString(ai._accountId());
}
String votableCategories = "";
if (votable != null) {
VotableInfo vi = votable.get(ai._accountId());
if (vi != null) {
Set<String> s = vi.votableLabels();
if (!s.isEmpty()) {
StringBuilder sb = new StringBuilder(Util.C.votable());
sb.append(" ");
for (Iterator<String> it = vi.votableLabels().iterator(); it.hasNext(); ) {
sb.append(it.next());
if (it.hasNext()) {
sb.append(", ");
}
}
votableCategories = sb.toString();
}
}
}
html.openSpan()
.setAttribute("role", "listitem")
.setAttribute(DATA_ID, ai._accountId())
.setAttribute("title", getTitle(ai, votableCategories))
.setStyleName(style.label_user());
if (label != null) {
html.setAttribute(DATA_VOTE, label);
}
if (img != null) {
html.openElement("img").setStyleName(style.avatar()).setAttribute("src", img.url());
if (img.width() > 0) {
html.setAttribute("width", img.width());
}
if (img.height() > 0) {
html.setAttribute("height", img.height());
}
html.closeSelf();
}
html.append(name);
if (removable.contains(ai._accountId())) {
html.openElement("button");
if (label != null) {
html.setAttribute("title", Util.M.removeVote(label))
.setAttribute("onclick", REMOVE_VOTE + "(event)");
} else {
html.setAttribute("title", Util.M.removeReviewer(name))
.setAttribute("onclick", REMOVE_REVIEWER + "(event)");
}
html.append("×").closeElement("button");
}
html.closeSpan();
if (itr.hasNext()) {
html.append(' ');
}
}
return html;
}
private static String getTitle(AccountInfo ai, String votableCategories) {
String title = ai.email() != null ? ai.email() : "";
if (!votableCategories.isEmpty()) {
if (!title.isEmpty()) {
title += " ";
}
title += votableCategories;
}
return title;
}
}