blob: 76fc72cf8c951dc8d99dc1ed3317f231dca08953 [file] [log] [blame]
// Copyright (C) 2015 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.googlesource.gerrit.plugins.labelui.client;
import com.google.gerrit.client.GerritUiExtensionPoint;
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.Natives;
import com.google.gerrit.plugin.client.Plugin;
import com.google.gerrit.plugin.client.extension.Panel;
import com.google.gerrit.plugin.client.rpc.RestApi;
import com.google.gwt.core.client.GWT;
import com.google.gwt.dom.client.Style;
import com.google.gwt.dom.client.Style.Cursor;
import com.google.gwt.dom.client.Style.Display;
import com.google.gwt.dom.client.Style.TextAlign;
import com.google.gwt.dom.client.Style.Unit;
import com.google.gwt.resources.client.ImageResource;
import com.google.gwt.user.client.rpc.AsyncCallback;
import com.google.gwt.user.client.ui.Grid;
import com.google.gwt.user.client.ui.HTMLTable.CellFormatter;
import com.google.gwt.user.client.ui.HorizontalPanel;
import com.google.gwt.user.client.ui.Image;
import com.google.gwt.user.client.ui.Label;
import com.google.gwt.user.client.ui.VerticalPanel;
import com.google.gwt.user.client.ui.Widget;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;
import java.util.TreeSet;
public class LabelPanel extends VerticalPanel {
static class Factory implements Panel.EntryPoint {
@Override
public void onLoad(Panel panel) {
panel.setWidget(new LabelPanel(panel));
}
}
private final static String COLOR_GREEN = "#060";
private final static String COLOR_RED = "#F00";
LabelPanel(final Panel panel) {
new RestApi("accounts").id("self").view(Plugin.get().getPluginName(), "preferences")
.get(new AsyncCallback<PreferencesInfo>() {
@Override
public void onSuccess(PreferencesInfo result) {
LabelUIPlugin.refreshDefaultLabelUi(result.ui());
ChangeInfo change =
panel.getObject(GerritUiExtensionPoint.Key.CHANGE_INFO).cast();
switch (result.ui()) {
case USER_LABEL_TABLE:
displayUserLabelTable(change);
break;
case DEFAULT:
default:
return;
}
}
@Override
public void onFailure(Throwable caught) {
// never invoked
}
});
}
private void displayUserLabelTable(ChangeInfo change) {
Set<String> labelNames = getLabelNames(change);
Map<String, AccountInfo> users = getUserMap(labelNames, change);
Map<Integer, VotableInfo> votable = votable(change);
Grid g = createGrid(users.size() + 1, labelNames.size() + 1);
int i = 1;
for (String labelName : labelNames) {
g.setWidget(0, i, createLabelLabel(change.label(labelName)));
i++;
}
i = 1;
for (AccountInfo account : users.values()) {
g.setWidget(i, 0, createUserWidget(account));
int j = 1;
for (String labelName : labelNames) {
LabelInfo label = change.label(labelName);
ApprovalInfo ai = label.forUser(account._accountId());
g.setWidget(i, j, createLabelValueWidget(label, ai));
if (!votable.get(account._accountId()).isVotable(labelName)) {
formatNonVotable(g, i, j);
}
j++;
}
i++;
}
add(g);
}
private static Grid createGrid(int rows, int columns) {
Grid g = new Grid(rows, columns);
g.addStyleName("infoBlock");
g.addStyleName("changeTable");
CellFormatter fmt = g.getCellFormatter();
fmt.addStyleName(0, 0, "leftMostCell");
fmt.addStyleName(0, 0, "topmost");
for (int c = 1; c < columns; c++) {
fmt.addStyleName(0, c, "header");
fmt.addStyleName(0, c, "topmost");
}
for (int r = 1; r < rows; r++) {
fmt.addStyleName(r, 0, "leftMostCell");
fmt.addStyleName(r, 0, "header");
for (int c = 1; c < columns; c++) {
fmt.addStyleName(r, c, "dataCell");
}
}
for (int c = 0; c < columns; c++) {
fmt.addStyleName(rows - 1, c, "bottomheader");
}
return g;
}
private static Set<String> getLabelNames(ChangeInfo change) {
return new TreeSet<String>(change.labels());
}
private static Map<String, AccountInfo> getUserMap(Set<String> labelNames,
ChangeInfo change) {
Map<String, AccountInfo> users = new TreeMap<>();
for (String labelName : labelNames) {
LabelInfo label = change.label(labelName);
for (ApprovalInfo ai : Natives.asList(label.all())) {
users.put(ai.name(), ai);
}
}
return users;
}
private static Widget createLabelValueWidget(LabelInfo label, ApprovalInfo ai) {
int accountId = ai._accountId();
String formattedValue = formatValue(ai.value());
String valueText = label.valueText(formattedValue);
if (label.approved() != null
&& label.approved()._accountId() == accountId) {
return createImage(LabelUIPlugin.RESOURCES.greenCheck(), valueText);
} else if (label.rejected() != null
&& label.rejected()._accountId() == accountId) {
return createImage(LabelUIPlugin.RESOURCES.redNot(), valueText);
} else {
return createValueLabel(formattedValue, valueText, ai.value());
}
}
private static Label createLabelLabel(LabelInfo label) {
Label l = new Label(label.name());
Style s = l.getElement().getStyle();
s.setCursor(Cursor.DEFAULT);
if (label.rejected() != null) {
s.setColor(COLOR_RED);
l.setTitle("Rejected by " + label.rejected().name());
} else if (label.approved() != null) {
s.setColor(COLOR_GREEN);
l.setTitle("Approved by " + label.approved().name());
}
return l;
}
private static Widget createUserWidget(AccountInfo account) {
HorizontalPanel p = new HorizontalPanel();
Label l = new Label(account.name());
if (account.hasAvatarInfo()) {
p.add(createAvatar(account));
l.getElement().getStyle().setMarginTop(3, Unit.PX);
}
l.getElement().getStyle().setCursor(Cursor.DEFAULT);
l.setTitle(formatToolTip(account));
p.add(l);
return p;
}
private static Image createAvatar(AccountInfo account) {
int size = 16;
AvatarInfo avatar = account.avatar(size);
if (avatar == null) {
avatar = account.avatar(AvatarInfo.DEFAULT_SIZE);
}
String url;
if (avatar == null) {
RestApi api =
new RestApi("/accounts/").id(account._accountId()).view("avatar")
.addParameter("s", size);
url = GWT.getHostPageBaseURL() + api.path().substring(1);
} else {
url = avatar.url();
}
Image avatarImage = new Image(url);
avatarImage.setSize("", size + "px");
return avatarImage;
}
private static String formatToolTip(AccountInfo ai) {
StringBuilder b = new StringBuilder();
b.append(ai.name());
if (ai.email() != null) {
b.append(" <");
b.append(ai.email());
b.append(">");
}
return b.toString();
}
private static Image createImage(ImageResource imageResource, String valueText) {
Image image = new Image(imageResource);
if (valueText != null) {
image.setTitle(valueText);
}
center(image);
return image;
}
private static void center(Image image) {
Style s = image.getElement().getStyle();
s.setProperty("margin-left", "auto");
s.setProperty("margin-right", "auto");
s.setDisplay(Display.BLOCK);
}
public static Label createValueLabel(String formattedValue, String valueText, short value) {
Label l = new Label(formattedValue);
if (valueText != null) {
l.setTitle(valueText);
}
Style s = l.getElement().getStyle();
s.setTextAlign(TextAlign.CENTER);
s.setCursor(Cursor.DEFAULT);
if (value > 0) {
s.setColor(COLOR_GREEN);
} else if (value < 0) {
s.setColor(COLOR_RED);
} else {
// make label invisible, we cannot omit it since we need the label to show
// a tooltip
s.setColor("transparent");
}
return l;
}
private static String formatValue(short value) {
if (value < 0) {
return Short.toString(value);
} else if (value == 0) {
return " 0";
} else {
return "+" + Short.toString(value);
}
}
private static void formatNonVotable(Grid g, int row, int column) {
Widget w = g.getWidget(row, column);
g.getCellFormatter().addStyleName(row, column, "header");
w.setTitle("cannot vote on this label");
}
private static Map<Integer, VotableInfo> votable(ChangeInfo change) {
Map<Integer, VotableInfo> d = new HashMap<>();
for (String name : change.labels()) {
LabelInfo label = change.label(name);
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 (ai.hasValue()) {
ad.votable(name);
}
}
}
}
return d;
}
private static class VotableInfo {
private Set<String> votable;
void votable(String label) {
if (votable == null) {
votable = new HashSet<>();
}
votable.add(label);
}
Set<String> votableLabels() {
Set<String> s = new HashSet<>();
if (votable != null) {
s.addAll(votable);
}
return s;
}
boolean isVotable(String label) {
return votableLabels().contains(label);
}
}
}