355 lines
12 KiB
Java
355 lines
12 KiB
Java
// 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;
|
||
}
|
||
}
|