/changes/: Display account dashboards
Replace the old JSON-RPC call showing a dashboard to instead be three queries using the newer /changes/ REST API. This moves all of the logic into the common ListChanges and QueryProcessor, and simplifies the server code. Like the All > Open view, the columns in the table now update dynamically to reflect the labels actually required to submit the changes displayed. Changes in the outgoing and incoming sections are sorted by project, branch, topic, and then the legacy change number. This gives a more predictable sorting when looking at a very busy dashboard, as users can scan the Project and Branch columns and work down the page until they locate the change(s) they are looking for. In particular it helps me when I want to look at changes for git-repo, or stable changes for gerrit near release time. Change-Id: Ia9e9fc103561b43fe0188958bfa226fdc21b6992
This commit is contained in:
@@ -14,39 +14,46 @@
|
|||||||
|
|
||||||
package com.google.gerrit.client.changes;
|
package com.google.gerrit.client.changes;
|
||||||
|
|
||||||
import com.google.gerrit.client.FormatUtil;
|
|
||||||
import com.google.gerrit.client.Gerrit;
|
import com.google.gerrit.client.Gerrit;
|
||||||
import com.google.gerrit.client.changes.ChangeTable.ApprovalViewType;
|
import com.google.gerrit.client.NotFoundScreen;
|
||||||
|
import com.google.gerrit.client.rpc.NativeList;
|
||||||
import com.google.gerrit.client.rpc.ScreenLoadCallback;
|
import com.google.gerrit.client.rpc.ScreenLoadCallback;
|
||||||
import com.google.gerrit.client.ui.Screen;
|
import com.google.gerrit.client.ui.Screen;
|
||||||
import com.google.gerrit.common.PageLinks;
|
import com.google.gerrit.common.PageLinks;
|
||||||
import com.google.gerrit.common.data.AccountDashboardInfo;
|
|
||||||
import com.google.gerrit.common.data.AccountInfo;
|
|
||||||
import com.google.gerrit.reviewdb.client.Account;
|
import com.google.gerrit.reviewdb.client.Account;
|
||||||
|
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.Comparator;
|
||||||
|
|
||||||
public class AccountDashboardScreen extends Screen implements ChangeListScreen {
|
public class AccountDashboardScreen extends Screen implements ChangeListScreen {
|
||||||
private final Account.Id ownerId;
|
private final Account.Id ownerId;
|
||||||
private ChangeTable table;
|
private final boolean mine;
|
||||||
private ChangeTable.Section byOwner;
|
private ChangeTable2 table;
|
||||||
private ChangeTable.Section forReview;
|
private ChangeTable2.Section outgoing;
|
||||||
private ChangeTable.Section closed;
|
private ChangeTable2.Section incoming;
|
||||||
|
private ChangeTable2.Section closed;
|
||||||
|
|
||||||
public AccountDashboardScreen(final Account.Id id) {
|
public AccountDashboardScreen(final Account.Id id) {
|
||||||
ownerId = id;
|
ownerId = id;
|
||||||
|
mine = Gerrit.isSignedIn() && ownerId.equals(Gerrit.getUserAccount().getId());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onInitUI() {
|
protected void onInitUI() {
|
||||||
super.onInitUI();
|
super.onInitUI();
|
||||||
table = new ChangeTable(true);
|
table = new ChangeTable2();
|
||||||
table.addStyleName(Gerrit.RESOURCES.css().accountDashboard());
|
table.addStyleName(Gerrit.RESOURCES.css().accountDashboard());
|
||||||
byOwner = new ChangeTable.Section("", ApprovalViewType.STRONGEST, null);
|
|
||||||
forReview = new ChangeTable.Section("", ApprovalViewType.USER, ownerId);
|
|
||||||
closed = new ChangeTable.Section("", ApprovalViewType.STRONGEST, null);
|
|
||||||
|
|
||||||
table.addSection(byOwner);
|
outgoing = new ChangeTable2.Section();
|
||||||
table.addSection(forReview);
|
incoming = new ChangeTable2.Section();
|
||||||
|
closed = new ChangeTable2.Section();
|
||||||
|
|
||||||
|
outgoing.setTitleText(Util.C.outgoingReviews());
|
||||||
|
incoming.setTitleText(Util.C.incomingReviews());
|
||||||
|
closed.setTitleText(Util.C.recentlyClosed());
|
||||||
|
|
||||||
|
table.addSection(outgoing);
|
||||||
|
table.addSection(incoming);
|
||||||
table.addSection(closed);
|
table.addSection(closed);
|
||||||
add(table);
|
add(table);
|
||||||
table.setSavePointerId(PageLinks.toAccountDashboard(ownerId));
|
table.setSavePointerId(PageLinks.toAccountDashboard(ownerId));
|
||||||
@@ -55,13 +62,17 @@ public class AccountDashboardScreen extends Screen implements ChangeListScreen {
|
|||||||
@Override
|
@Override
|
||||||
protected void onLoad() {
|
protected void onLoad() {
|
||||||
super.onLoad();
|
super.onLoad();
|
||||||
Util.LIST_SVC.forAccount(ownerId,
|
String who = mine ? "self" : ownerId.toString();
|
||||||
new ScreenLoadCallback<AccountDashboardInfo>(this) {
|
ChangeList.query(
|
||||||
|
new ScreenLoadCallback<NativeList<ChangeList>>(this) {
|
||||||
@Override
|
@Override
|
||||||
protected void preDisplay(final AccountDashboardInfo r) {
|
protected void preDisplay(NativeList<ChangeList> result) {
|
||||||
display(r);
|
display(result);
|
||||||
}
|
}
|
||||||
});
|
},
|
||||||
|
"is:open owner:" + who,
|
||||||
|
"is:open reviewer:" + who + " -owner:" + who,
|
||||||
|
"is:closed owner:" + who + " -age:1w limit:10");
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -70,20 +81,80 @@ public class AccountDashboardScreen extends Screen implements ChangeListScreen {
|
|||||||
table.setRegisterKeys(true);
|
table.setRegisterKeys(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void display(final AccountDashboardInfo r) {
|
private void display(NativeList<ChangeList> result) {
|
||||||
table.setAccountInfoCache(r.getAccounts());
|
if (!mine && !hasChanges(result)) {
|
||||||
|
// When no results are returned and the data is not for the
|
||||||
|
// current user, the target user is presumed to not exist.
|
||||||
|
Gerrit.display(getToken(), new NotFoundScreen());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
final AccountInfo o = r.getAccounts().get(r.getOwner());
|
ChangeList out = result.get(0);
|
||||||
final String name = FormatUtil.name(o);
|
ChangeList in = result.get(1);
|
||||||
setWindowTitle(name);
|
ChangeList done = result.get(2);
|
||||||
setPageTitle(Util.M.accountDashboardTitle(name));
|
|
||||||
byOwner.setTitleText(Util.M.changesStartedBy(name));
|
|
||||||
forReview.setTitleText(Util.M.changesReviewableBy(name));
|
|
||||||
closed.setTitleText(Util.C.changesRecentlyClosed());
|
|
||||||
|
|
||||||
byOwner.display(r.getByOwner());
|
if (mine) {
|
||||||
forReview.display(r.getForReview());
|
setWindowTitle(Util.C.myDashboardTitle());
|
||||||
closed.display(r.getClosed());
|
setPageTitle(Util.C.myDashboardTitle());
|
||||||
|
} else {
|
||||||
|
// The server doesn't tell us who the dashboard is for. Try to guess
|
||||||
|
// by looking at a change started by the owner and extract the name.
|
||||||
|
String name = guessName(out);
|
||||||
|
if (name == null) {
|
||||||
|
name = guessName(done);
|
||||||
|
}
|
||||||
|
if (name != null) {
|
||||||
|
setWindowTitle(name);
|
||||||
|
setPageTitle(Util.M.accountDashboardTitle(name));
|
||||||
|
} else {
|
||||||
|
setWindowTitle(Util.C.unknownDashboardTitle());
|
||||||
|
setWindowTitle(Util.C.unknownDashboardTitle());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Collections.sort(out.asList(), compare());
|
||||||
|
Collections.sort(in.asList(), compare());
|
||||||
|
|
||||||
|
table.updateColumnsForLabels(out, in, done);
|
||||||
|
outgoing.display(out);
|
||||||
|
incoming.display(in);
|
||||||
|
closed.display(done);
|
||||||
table.finishDisplay();
|
table.finishDisplay();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private Comparator<ChangeInfo> compare() {
|
||||||
|
return new Comparator<ChangeInfo>() {
|
||||||
|
@Override
|
||||||
|
public int compare(ChangeInfo a, ChangeInfo b) {
|
||||||
|
int cmp = a.project().compareTo(b.project());
|
||||||
|
if (cmp != 0) return cmp;
|
||||||
|
cmp = a.branch().compareTo(b.branch());
|
||||||
|
if (cmp != 0) return cmp;
|
||||||
|
|
||||||
|
String at = a.topic() != null ? a.topic() : "";
|
||||||
|
String bt = b.topic() != null ? b.topic() : "";
|
||||||
|
cmp = at.compareTo(bt);
|
||||||
|
if (cmp != 0) return cmp;
|
||||||
|
return a._number() - b._number();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean hasChanges(NativeList<ChangeList> result) {
|
||||||
|
for (ChangeList list : result.asList()) {
|
||||||
|
if (!list.isEmpty()) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String guessName(ChangeList list) {
|
||||||
|
for (ChangeInfo change : list.asList()) {
|
||||||
|
if (change.owner() != null && change.owner().name() != null) {
|
||||||
|
return change.owner().name();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -23,7 +23,11 @@ public interface ChangeConstants extends Constants {
|
|||||||
String statusLongAbandoned();
|
String statusLongAbandoned();
|
||||||
String statusLongDraft();
|
String statusLongDraft();
|
||||||
|
|
||||||
String changesRecentlyClosed();
|
String myDashboardTitle();
|
||||||
|
String unknownDashboardTitle();
|
||||||
|
String incomingReviews();
|
||||||
|
String outgoingReviews();
|
||||||
|
String recentlyClosed();
|
||||||
|
|
||||||
String starredHeading();
|
String starredHeading();
|
||||||
String watchedHeading();
|
String watchedHeading();
|
||||||
|
|||||||
@@ -7,7 +7,11 @@ statusLongDraft = Draft
|
|||||||
starredHeading = Starred Changes
|
starredHeading = Starred Changes
|
||||||
watchedHeading = Open Changes of Watched Projects
|
watchedHeading = Open Changes of Watched Projects
|
||||||
draftsHeading = Changes with unpublished drafts
|
draftsHeading = Changes with unpublished drafts
|
||||||
changesRecentlyClosed = Recently closed
|
myDashboardTitle = My Reviews
|
||||||
|
unknownDashboardTitle = Code Review Dashboard
|
||||||
|
incomingReviews = Incoming reviews
|
||||||
|
outgoingReviews = Outgoing reviews
|
||||||
|
recentlyClosed = Recently closed
|
||||||
allOpenChanges = All open changes
|
allOpenChanges = All open changes
|
||||||
allAbandonedChanges = All abandoned changes
|
allAbandonedChanges = All abandoned changes
|
||||||
allMergedChanges = All merged changes
|
allMergedChanges = All merged changes
|
||||||
|
|||||||
@@ -23,6 +23,17 @@ import com.google.gwtorm.client.KeyUtil;
|
|||||||
public class ChangeList extends NativeList<ChangeInfo> {
|
public class ChangeList extends NativeList<ChangeInfo> {
|
||||||
private static final String URI = "/changes/";
|
private static final String URI = "/changes/";
|
||||||
|
|
||||||
|
/** Run 2 or more queries in a single remote invocation. */
|
||||||
|
public static void query(
|
||||||
|
AsyncCallback<NativeList<ChangeList>> callback, String... queries) {
|
||||||
|
assert queries.length >= 2; // At least 2 is required for correct result.
|
||||||
|
RestApi call = new RestApi(URI);
|
||||||
|
for (String q : queries) {
|
||||||
|
call.addParameterRaw("q", KeyUtil.encode(q));
|
||||||
|
}
|
||||||
|
call.send(callback);
|
||||||
|
}
|
||||||
|
|
||||||
public static void prev(String query,
|
public static void prev(String query,
|
||||||
int limit, String sortkey,
|
int limit, String sortkey,
|
||||||
AsyncCallback<ChangeList> callback) {
|
AsyncCallback<ChangeList> callback) {
|
||||||
|
|||||||
@@ -18,8 +18,6 @@ import com.google.gwt.i18n.client.Messages;
|
|||||||
|
|
||||||
public interface ChangeMessages extends Messages {
|
public interface ChangeMessages extends Messages {
|
||||||
String accountDashboardTitle(String fullName);
|
String accountDashboardTitle(String fullName);
|
||||||
String changesStartedBy(String fullName);
|
|
||||||
String changesReviewableBy(String fullName);
|
|
||||||
String changesOpenInProject(String string);
|
String changesOpenInProject(String string);
|
||||||
String changesMergedInProject(String string);
|
String changesMergedInProject(String string);
|
||||||
String changesAbandonedInProject(String string);
|
String changesAbandonedInProject(String string);
|
||||||
|
|||||||
@@ -1,6 +1,4 @@
|
|||||||
accountDashboardTitle = Code Review Dashboard for {0}
|
accountDashboardTitle = Code Review Dashboard for {0}
|
||||||
changesStartedBy = Started by {0}
|
|
||||||
changesReviewableBy = Review Requests for {0}
|
|
||||||
changesOpenInProject = Open Changes In {0}
|
changesOpenInProject = Open Changes In {0}
|
||||||
changesMergedInProject = Merged Changes In {0}
|
changesMergedInProject = Merged Changes In {0}
|
||||||
changesAbandonedInProject = Abandoned Changes In {0}
|
changesAbandonedInProject = Abandoned Changes In {0}
|
||||||
|
|||||||
@@ -145,12 +145,14 @@ public class ChangeTable2 extends NavigationTable<ChangeInfo> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void updateTableLayoutForLabels(ChangeList list) {
|
public void updateColumnsForLabels(ChangeList... lists) {
|
||||||
labelNames = new ArrayList<String>();
|
labelNames = new ArrayList<String>();
|
||||||
for (int i = 0; i < list.size(); i++) {
|
for (ChangeList list : lists) {
|
||||||
for (String name : list.get(i).labels()) {
|
for (int i = 0; i < list.size(); i++) {
|
||||||
if (!labelNames.contains(name)) {
|
for (String name : list.get(i).labels()) {
|
||||||
labelNames.add(name);
|
if (!labelNames.contains(name)) {
|
||||||
|
labelNames.add(name);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -177,6 +179,12 @@ public class ChangeTable2 extends NavigationTable<ChangeInfo> {
|
|||||||
table.getCellFormatter().getElement(0, col).setTitle(name);
|
table.getCellFormatter().getElement(0, col).setTitle(name);
|
||||||
fmt.addStyleName(0, col, Gerrit.RESOURCES.css().dataHeader());
|
fmt.addStyleName(0, col, Gerrit.RESOURCES.css().dataHeader());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for (Section s : sections) {
|
||||||
|
if (s.titleRow >= 0) {
|
||||||
|
fmt.setColSpan(s.titleRow, 0, columns);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void populateChangeRow(final int row, final ChangeInfo c) {
|
private void populateChangeRow(final int row, final ChangeInfo c) {
|
||||||
@@ -374,20 +382,15 @@ public class ChangeTable2 extends NavigationTable<ChangeInfo> {
|
|||||||
parent.removeRow(dataBegin);
|
parent.removeRow(dataBegin);
|
||||||
rows--;
|
rows--;
|
||||||
}
|
}
|
||||||
}
|
} else {
|
||||||
|
|
||||||
if (sz == 0) {
|
|
||||||
if (hadData) {
|
|
||||||
parent.insertNoneRow(dataBegin);
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!hadData) {
|
|
||||||
parent.removeRow(dataBegin);
|
parent.removeRow(dataBegin);
|
||||||
}
|
}
|
||||||
|
|
||||||
parent.updateTableLayoutForLabels(changeList);
|
if (sz == 0) {
|
||||||
|
parent.insertNoneRow(dataBegin);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
while (rows < sz) {
|
while (rows < sz) {
|
||||||
parent.insertChangeRow(dataBegin + rows);
|
parent.insertChangeRow(dataBegin + rows);
|
||||||
rows++;
|
rows++;
|
||||||
|
|||||||
@@ -131,6 +131,7 @@ public abstract class PagedSingleListScreen extends Screen {
|
|||||||
next.setVisible(l._more_changes());
|
next.setVisible(l._more_changes());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
table.updateColumnsForLabels(result);
|
||||||
section.display(result);
|
section.display(result);
|
||||||
table.finishDisplay();
|
table.finishDisplay();
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user