Filter the list of open changes by watched projects
Users are normally not interested in all projects that are hosted in Gerrit, but only in some of them. If a user is interested in a project he normally adds this project to the watch list, so that he can get e-mail notification if something changes. This change provides a new screen in Gerrit that shows all open changes for only watched projects. For the user this means to have one list that shows all open changes that are relevant for him and that he potentially wants to review. The new screen is available under 'My -> Watched Changes'. Change-Id: I8814d9ce106fdf7ef4312ae7ec3ba670f6678dd2 Signed-off-by: Edwin Kempin <edwin.kempin@gmail.com>
This commit is contained in:
committed by
Shawn O. Pearce
parent
ac8f93afe7
commit
33cc5a9053
@@ -33,11 +33,13 @@ public class PageLinks {
|
||||
public static final String SETTINGS_NEW_AGREEMENT = "settings,new-agreement";
|
||||
public static final String REGISTER = "register";
|
||||
|
||||
public static final String TOP = "n,z";
|
||||
|
||||
public static final String MINE = "mine";
|
||||
public static final String MINE_STARRED = "mine,starred";
|
||||
public static final String MINE_DRAFTS = "mine,drafts";
|
||||
public static final String MINE_WATCHED = "mine,watched," + TOP;
|
||||
|
||||
public static final String TOP = "n,z";
|
||||
public static final String ALL_ABANDONED = "all,abandoned," + TOP;
|
||||
public static final String ALL_MERGED = "all,merged," + TOP;
|
||||
public static final String ALL_OPEN = "all,open," + TOP;
|
||||
|
||||
@@ -36,6 +36,14 @@ public interface ChangeListService extends RemoteJsonService {
|
||||
void allOpenNext(String pos, int limit,
|
||||
AsyncCallback<SingleListChangeInfo> callback);
|
||||
|
||||
@SignInRequired
|
||||
void myWatchedOpenPrev(String pos, int limit,
|
||||
AsyncCallback<SingleListChangeInfo> callback);
|
||||
|
||||
@SignInRequired
|
||||
void myWatchedOpenNext(String pos, int limit,
|
||||
AsyncCallback<SingleListChangeInfo> callback);
|
||||
|
||||
/** Get all open changes more recent than pos, fetching at most limit rows. */
|
||||
void byProjectOpenPrev(Project.NameKey project, String pos, int limit,
|
||||
AsyncCallback<SingleListChangeInfo> callback);
|
||||
|
||||
@@ -39,6 +39,7 @@ import com.google.gerrit.client.changes.AccountDashboardScreen;
|
||||
import com.google.gerrit.client.changes.AllAbandonedChangesScreen;
|
||||
import com.google.gerrit.client.changes.AllMergedChangesScreen;
|
||||
import com.google.gerrit.client.changes.AllOpenChangesScreen;
|
||||
import com.google.gerrit.client.changes.MineWatchedOpenChangesScreen;
|
||||
import com.google.gerrit.client.changes.ByProjectAbandonedChangesScreen;
|
||||
import com.google.gerrit.client.changes.ByProjectMergedChangesScreen;
|
||||
import com.google.gerrit.client.changes.ByProjectOpenChangesScreen;
|
||||
@@ -154,6 +155,11 @@ public class Dispatcher {
|
||||
return new MineDraftsScreen();
|
||||
|
||||
} else {
|
||||
String p = "mine,watched,";
|
||||
if (token.startsWith(p)) {
|
||||
return new MineWatchedOpenChangesScreen(skip(p, token));
|
||||
}
|
||||
|
||||
return new NotFoundScreen();
|
||||
}
|
||||
}
|
||||
@@ -175,6 +181,7 @@ public class Dispatcher {
|
||||
if (token.startsWith(p)) {
|
||||
return new AllOpenChangesScreen(skip(p, token));
|
||||
}
|
||||
|
||||
return new NotFoundScreen();
|
||||
}
|
||||
|
||||
|
||||
@@ -410,6 +410,7 @@ public class Gerrit implements EntryPoint {
|
||||
m = new LinkMenuBar();
|
||||
addLink(m, C.menuMyChanges(), PageLinks.MINE);
|
||||
addLink(m, C.menuMyDrafts(), PageLinks.MINE_DRAFTS);
|
||||
addLink(m, C.menuMyWatchedChanges(), PageLinks.MINE_WATCHED);
|
||||
addLink(m, C.menuMyStarredChanges(), PageLinks.MINE_STARRED);
|
||||
menuLeft.add(m, C.menuMine());
|
||||
menuLeft.selectTab(1);
|
||||
|
||||
@@ -48,6 +48,7 @@ public interface GerritConstants extends Constants {
|
||||
String menuMine();
|
||||
String menuMyChanges();
|
||||
String menuMyDrafts();
|
||||
String menuMyWatchedChanges();
|
||||
String menuMyStarredChanges();
|
||||
|
||||
String menuAdmin();
|
||||
@@ -76,5 +77,6 @@ public interface GerritConstants extends Constants {
|
||||
String jumpAllMerged();
|
||||
String jumpMine();
|
||||
String jumpMineDrafts();
|
||||
String jumpMineWatched();
|
||||
String jumpMineStarred();
|
||||
}
|
||||
|
||||
@@ -32,6 +32,7 @@ menuMine = My
|
||||
menuMyChanges = Changes
|
||||
menuMyDrafts = Drafts
|
||||
menuMyStarredChanges = Starred Changes
|
||||
menuMyWatchedChanges = Watched Changes
|
||||
|
||||
menuAdmin = Admin
|
||||
menuPeople = People
|
||||
@@ -58,5 +59,6 @@ sectionJumping = Jumping
|
||||
jumpAllOpen = Go to all open changes
|
||||
jumpAllMerged = Go to all merged changes
|
||||
jumpMine = Go to my dashboard
|
||||
jumpMineWatched = Go to watched changes
|
||||
jumpMineDrafts = Go to drafts
|
||||
jumpMineStarred = Go to starred changes
|
||||
|
||||
@@ -52,6 +52,12 @@ class JumpKeys {
|
||||
Gerrit.display(PageLinks.MINE_DRAFTS);
|
||||
}
|
||||
});
|
||||
jumps.add(new KeyCommand(0, 'w', Gerrit.C.jumpMineWatched()) {
|
||||
@Override
|
||||
public void onKeyPress(final KeyPressEvent event) {
|
||||
Gerrit.display(PageLinks.MINE_WATCHED);
|
||||
}
|
||||
});
|
||||
jumps.add(new KeyCommand(0, 's', Gerrit.C.jumpMineStarred()) {
|
||||
@Override
|
||||
public void onKeyPress(final KeyPressEvent event) {
|
||||
|
||||
@@ -18,7 +18,7 @@ import com.google.gerrit.client.Gerrit;
|
||||
import com.google.gerrit.reviewdb.Change;
|
||||
|
||||
|
||||
public class AllAbandonedChangesScreen extends AllSingleListScreen {
|
||||
public class AllAbandonedChangesScreen extends PagedSingleListScreen {
|
||||
public AllAbandonedChangesScreen(final String positionToken) {
|
||||
super("all,abandoned", positionToken);
|
||||
}
|
||||
|
||||
@@ -18,7 +18,7 @@ import com.google.gerrit.client.Gerrit;
|
||||
import com.google.gerrit.reviewdb.Change;
|
||||
|
||||
|
||||
public class AllMergedChangesScreen extends AllSingleListScreen {
|
||||
public class AllMergedChangesScreen extends PagedSingleListScreen {
|
||||
public AllMergedChangesScreen(final String positionToken) {
|
||||
super("all,merged", positionToken);
|
||||
}
|
||||
|
||||
@@ -18,7 +18,7 @@ import com.google.gerrit.client.Gerrit;
|
||||
|
||||
|
||||
|
||||
public class AllOpenChangesScreen extends AllSingleListScreen {
|
||||
public class AllOpenChangesScreen extends PagedSingleListScreen {
|
||||
public AllOpenChangesScreen(final String positionToken) {
|
||||
super("all,open", positionToken);
|
||||
}
|
||||
|
||||
@@ -18,7 +18,7 @@ import com.google.gerrit.reviewdb.Change;
|
||||
import com.google.gerrit.reviewdb.Project;
|
||||
|
||||
|
||||
public class ByProjectAbandonedChangesScreen extends AllSingleListScreen {
|
||||
public class ByProjectAbandonedChangesScreen extends PagedSingleListScreen {
|
||||
private final Project.NameKey projectKey;
|
||||
|
||||
public ByProjectAbandonedChangesScreen(final Project.NameKey proj,
|
||||
|
||||
@@ -18,7 +18,7 @@ import com.google.gerrit.reviewdb.Change;
|
||||
import com.google.gerrit.reviewdb.Project;
|
||||
|
||||
|
||||
public class ByProjectMergedChangesScreen extends AllSingleListScreen {
|
||||
public class ByProjectMergedChangesScreen extends PagedSingleListScreen {
|
||||
private final Project.NameKey projectKey;
|
||||
|
||||
public ByProjectMergedChangesScreen(final Project.NameKey proj,
|
||||
|
||||
@@ -17,7 +17,7 @@ package com.google.gerrit.client.changes;
|
||||
import com.google.gerrit.reviewdb.Project;
|
||||
|
||||
|
||||
public class ByProjectOpenChangesScreen extends AllSingleListScreen {
|
||||
public class ByProjectOpenChangesScreen extends PagedSingleListScreen {
|
||||
private final Project.NameKey projectKey;
|
||||
|
||||
public ByProjectOpenChangesScreen(final Project.NameKey proj,
|
||||
|
||||
@@ -25,6 +25,7 @@ public interface ChangeConstants extends Constants {
|
||||
String changesRecentlyClosed();
|
||||
|
||||
String starredHeading();
|
||||
String watchedHeading();
|
||||
String draftsHeading();
|
||||
String allOpenChanges();
|
||||
String allAbandonedChanges();
|
||||
|
||||
@@ -4,6 +4,7 @@ statusLongMerged = Merged
|
||||
statusLongAbandoned = Abandoned
|
||||
|
||||
starredHeading = Starred Changes
|
||||
watchedHeading = Open Changes of Watched Projects
|
||||
draftsHeading = Changes with unpublished drafts
|
||||
changesRecentlyClosed = Recently closed
|
||||
allOpenChanges = All open changes
|
||||
|
||||
@@ -24,7 +24,7 @@ import com.google.gwtorm.client.KeyUtil;
|
||||
|
||||
|
||||
|
||||
public class ChangeQueryResultsScreen extends AllSingleListScreen {
|
||||
public class ChangeQueryResultsScreen extends PagedSingleListScreen {
|
||||
private final String query;
|
||||
|
||||
public ChangeQueryResultsScreen(final String encQuery,
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
// Copyright (C) 2010 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.Gerrit;
|
||||
|
||||
public class MineWatchedOpenChangesScreen extends PagedSingleListScreen {
|
||||
public MineWatchedOpenChangesScreen(final String positionToken) {
|
||||
super("mine,watched", positionToken);
|
||||
setRequiresSignIn(true);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onInitUI() {
|
||||
super.onInitUI();
|
||||
setWindowTitle(Gerrit.C.menuMyWatchedChanges());
|
||||
setPageTitle(Util.C.watchedHeading());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void loadPrev() {
|
||||
Util.LIST_SVC.myWatchedOpenPrev(pos, pageSize, loadCallback());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void loadNext() {
|
||||
Util.LIST_SVC.myWatchedOpenNext(pos, pageSize, loadCallback());
|
||||
}
|
||||
}
|
||||
@@ -31,7 +31,7 @@ import com.google.gwtexpui.globalkey.client.KeyCommand;
|
||||
import java.util.List;
|
||||
|
||||
|
||||
public abstract class AllSingleListScreen extends Screen {
|
||||
public abstract class PagedSingleListScreen extends Screen {
|
||||
protected static final String MIN_SORTKEY = "";
|
||||
protected static final String MAX_SORTKEY = "z";
|
||||
|
||||
@@ -46,7 +46,7 @@ public abstract class AllSingleListScreen extends Screen {
|
||||
protected boolean useLoadPrev;
|
||||
protected String pos;
|
||||
|
||||
protected AllSingleListScreen(final String anchorToken,
|
||||
protected PagedSingleListScreen(final String anchorToken,
|
||||
final String positionToken) {
|
||||
anchorPrefix = anchorToken;
|
||||
useLoadPrev = positionToken.startsWith("p,");
|
||||
@@ -64,6 +64,7 @@ class UrlModule extends ServletModule {
|
||||
serve("/mine").with(screen(PageLinks.MINE));
|
||||
serve("/open").with(screen(PageLinks.ALL_OPEN));
|
||||
serve("/settings").with(screen(PageLinks.SETTINGS));
|
||||
serve("/watched").with(screen(PageLinks.MINE_WATCHED));
|
||||
serve("/starred").with(screen(PageLinks.MINE_STARRED));
|
||||
|
||||
serveRegex( //
|
||||
|
||||
@@ -36,6 +36,7 @@ import com.google.gerrit.reviewdb.StarredChange;
|
||||
import com.google.gerrit.reviewdb.TrackingId;
|
||||
import com.google.gerrit.server.CurrentUser;
|
||||
import com.google.gerrit.server.account.AccountInfoCacheFactory;
|
||||
import com.google.gerrit.server.config.WildProjectName;
|
||||
import com.google.gerrit.server.project.ChangeControl;
|
||||
import com.google.gerrit.server.project.NoSuchChangeException;
|
||||
import com.google.gwt.user.client.rpc.AsyncCallback;
|
||||
@@ -89,16 +90,19 @@ public class ChangeListServiceImpl extends BaseServiceImplementation implements
|
||||
private final Provider<CurrentUser> currentUser;
|
||||
private final ChangeControl.Factory changeControlFactory;
|
||||
private final AccountInfoCacheFactory.Factory accountInfoCacheFactory;
|
||||
private final Project.NameKey wildProject;
|
||||
|
||||
@Inject
|
||||
ChangeListServiceImpl(final Provider<ReviewDb> schema,
|
||||
final Provider<CurrentUser> currentUser,
|
||||
final ChangeControl.Factory changeControlFactory,
|
||||
final AccountInfoCacheFactory.Factory accountInfoCacheFactory) {
|
||||
final AccountInfoCacheFactory.Factory accountInfoCacheFactory,
|
||||
final @WildProjectName Project.NameKey wildProject) {
|
||||
super(schema, currentUser);
|
||||
this.currentUser = currentUser;
|
||||
this.changeControlFactory = changeControlFactory;
|
||||
this.accountInfoCacheFactory = accountInfoCacheFactory;
|
||||
this.wildProject = wildProject;
|
||||
}
|
||||
|
||||
private boolean canRead(final Change c) {
|
||||
@@ -131,6 +135,43 @@ public class ChangeListServiceImpl extends BaseServiceImplementation implements
|
||||
});
|
||||
}
|
||||
|
||||
public void myWatchedOpenPrev(final String pos, final int pageSize,
|
||||
final AsyncCallback<SingleListChangeInfo> callback) {
|
||||
run(callback, new QueryPrev(pageSize, pos) {
|
||||
@Override
|
||||
ResultSet<Change> query(ReviewDb db, int slim, String sortKey)
|
||||
throws OrmException {
|
||||
return db.changes().allOpenPrev(sortKey, slim);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean accept(Change c) {
|
||||
return isWatched(c);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public void myWatchedOpenNext(final String pos, final int pageSize,
|
||||
final AsyncCallback<SingleListChangeInfo> callback) {
|
||||
run(callback, new QueryNext(pageSize, pos) {
|
||||
@Override
|
||||
ResultSet<Change> query(ReviewDb db, int slim, String sortKey)
|
||||
throws OrmException {
|
||||
return db.changes().allOpenNext(sortKey, slim);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean accept(Change c) {
|
||||
return isWatched(c);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private boolean isWatched(Change c) {
|
||||
Set<Project.NameKey> watchedProjects = currentUser.get().getWatchedProjects();
|
||||
return watchedProjects.contains(c.getProject()) || watchedProjects.contains(wildProject);
|
||||
}
|
||||
|
||||
public void byProjectOpenPrev(final Project.NameKey project,
|
||||
final String pos, final int pageSize,
|
||||
final AsyncCallback<SingleListChangeInfo> callback) {
|
||||
@@ -569,7 +610,7 @@ public class ChangeListServiceImpl extends BaseServiceImplementation implements
|
||||
final ResultSet<Change> rs = query(db, slim, sortKey);
|
||||
for (final Change c : rs) {
|
||||
results = true;
|
||||
if (canRead(c)) {
|
||||
if (canRead(c) && accept(c)) {
|
||||
final ChangeInfo ci = new ChangeInfo(c);
|
||||
ac.want(ci.getOwner());
|
||||
ci.setStarred(starred.contains(ci.getId()));
|
||||
@@ -589,6 +630,10 @@ public class ChangeListServiceImpl extends BaseServiceImplementation implements
|
||||
return d;
|
||||
}
|
||||
|
||||
protected boolean accept(final Change c) {
|
||||
return true;
|
||||
}
|
||||
|
||||
boolean finish(final ArrayList<ChangeInfo> list) {
|
||||
final boolean atEnd = list.size() <= limit;
|
||||
if (list.size() == slim) {
|
||||
|
||||
@@ -16,6 +16,7 @@ package com.google.gerrit.server;
|
||||
|
||||
import com.google.gerrit.reviewdb.AccountGroup;
|
||||
import com.google.gerrit.reviewdb.Change;
|
||||
import com.google.gerrit.reviewdb.Project.NameKey;
|
||||
import com.google.gerrit.server.config.AuthConfig;
|
||||
import com.google.inject.Inject;
|
||||
import com.google.inject.Singleton;
|
||||
@@ -41,6 +42,11 @@ public class AnonymousUser extends CurrentUser {
|
||||
return Collections.emptySet();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<NameKey> getWatchedProjects() {
|
||||
return Collections.emptySet();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "ANONYMOUS";
|
||||
|
||||
@@ -16,6 +16,7 @@ package com.google.gerrit.server;
|
||||
|
||||
import com.google.gerrit.reviewdb.AccountGroup;
|
||||
import com.google.gerrit.reviewdb.Change;
|
||||
import com.google.gerrit.reviewdb.Project;
|
||||
import com.google.gerrit.server.config.AuthConfig;
|
||||
import com.google.inject.servlet.RequestScoped;
|
||||
|
||||
@@ -59,6 +60,9 @@ public abstract class CurrentUser {
|
||||
/** Set of changes starred by this user. */
|
||||
public abstract Set<Change.Id> getStarredChanges();
|
||||
|
||||
/** Set of project that are watched by this user */
|
||||
public abstract Set<Project.NameKey> getWatchedProjects();
|
||||
|
||||
/** Is the user a non-interactive user? */
|
||||
public boolean isBatchUser() {
|
||||
return getEffectiveGroups().contains(authConfig.getBatchUsersGroup());
|
||||
|
||||
@@ -16,9 +16,12 @@ package com.google.gerrit.server;
|
||||
|
||||
import com.google.gerrit.reviewdb.Account;
|
||||
import com.google.gerrit.reviewdb.AccountGroup;
|
||||
import com.google.gerrit.reviewdb.AccountProjectWatch;
|
||||
import com.google.gerrit.reviewdb.Change;
|
||||
import com.google.gerrit.reviewdb.Project;
|
||||
import com.google.gerrit.reviewdb.ReviewDb;
|
||||
import com.google.gerrit.reviewdb.StarredChange;
|
||||
import com.google.gerrit.reviewdb.Project.NameKey;
|
||||
import com.google.gerrit.server.account.AccountCache;
|
||||
import com.google.gerrit.server.account.AccountState;
|
||||
import com.google.gerrit.server.account.Realm;
|
||||
@@ -138,6 +141,7 @@ public class IdentifiedUser extends CurrentUser {
|
||||
private Set<String> emailAddresses;
|
||||
private Set<AccountGroup.Id> effectiveGroups;
|
||||
private Set<Change.Id> starredChanges;
|
||||
private Set<Project.NameKey> watchedProjects;
|
||||
|
||||
private IdentifiedUser(final AccessPath accessPath,
|
||||
final AuthConfig authConfig, final Provider<String> canonicalUrl,
|
||||
@@ -216,6 +220,27 @@ public class IdentifiedUser extends CurrentUser {
|
||||
return starredChanges;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<Project.NameKey> getWatchedProjects() {
|
||||
if (watchedProjects == null) {
|
||||
if (dbProvider == null) {
|
||||
throw new OutOfScopeException("Not in request scoped user");
|
||||
}
|
||||
final Set<Project.NameKey> h = new HashSet<Project.NameKey>();
|
||||
try {
|
||||
for (AccountProjectWatch projectWatch : dbProvider.get()
|
||||
.accountProjectWatches().byAccount(getAccountId())) {
|
||||
h.add(projectWatch.getProjectNameKey());
|
||||
}
|
||||
} catch (OrmException e) {
|
||||
log.warn("Cannot query project watches of a user", e);
|
||||
}
|
||||
watchedProjects = Collections.unmodifiableSet(h);
|
||||
}
|
||||
|
||||
return watchedProjects;
|
||||
}
|
||||
|
||||
public PersonIdent newRefLogIdent() {
|
||||
return newRefLogIdent(new Date(), TimeZone.getDefault());
|
||||
}
|
||||
|
||||
@@ -16,6 +16,7 @@ package com.google.gerrit.server;
|
||||
|
||||
import com.google.gerrit.reviewdb.AccountGroup;
|
||||
import com.google.gerrit.reviewdb.Change;
|
||||
import com.google.gerrit.reviewdb.Project.NameKey;
|
||||
import com.google.gerrit.server.config.AuthConfig;
|
||||
import com.google.inject.Inject;
|
||||
import com.google.inject.assistedinject.Assisted;
|
||||
@@ -57,6 +58,11 @@ public class PeerDaemonUser extends CurrentUser {
|
||||
return Collections.emptySet();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<NameKey> getWatchedProjects() {
|
||||
return Collections.emptySet();
|
||||
}
|
||||
|
||||
public SocketAddress getRemoteAddress() {
|
||||
return peer;
|
||||
}
|
||||
|
||||
@@ -16,6 +16,7 @@ package com.google.gerrit.server;
|
||||
|
||||
import com.google.gerrit.reviewdb.AccountGroup;
|
||||
import com.google.gerrit.reviewdb.Change;
|
||||
import com.google.gerrit.reviewdb.Project.NameKey;
|
||||
import com.google.gerrit.server.config.AuthConfig;
|
||||
import com.google.inject.Inject;
|
||||
import com.google.inject.assistedinject.Assisted;
|
||||
@@ -71,6 +72,11 @@ public class ReplicationUser extends CurrentUser {
|
||||
return Collections.emptySet();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<NameKey> getWatchedProjects() {
|
||||
return Collections.emptySet();
|
||||
}
|
||||
|
||||
public boolean isEverythingVisible() {
|
||||
return getEffectiveGroups() == EVERYTHING_VISIBLE;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user