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:
Edwin Kempin
2010-06-24 13:58:12 +02:00
committed by Shawn O. Pearce
parent ac8f93afe7
commit 33cc5a9053
25 changed files with 176 additions and 12 deletions

View File

@@ -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;

View File

@@ -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);

View File

@@ -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();
}

View File

@@ -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);

View File

@@ -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();
}

View File

@@ -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

View File

@@ -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) {

View File

@@ -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);
}

View File

@@ -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);
}

View File

@@ -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);
}

View File

@@ -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,

View File

@@ -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,

View File

@@ -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,

View File

@@ -25,6 +25,7 @@ public interface ChangeConstants extends Constants {
String changesRecentlyClosed();
String starredHeading();
String watchedHeading();
String draftsHeading();
String allOpenChanges();
String allAbandonedChanges();

View File

@@ -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

View File

@@ -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,

View File

@@ -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());
}
}

View File

@@ -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,");

View File

@@ -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( //

View File

@@ -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) {

View File

@@ -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";

View File

@@ -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());

View File

@@ -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());
}

View File

@@ -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;
}

View File

@@ -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;
}