Implement the ChangeListService in terms of ReviewDb's ChangeAccess

We now support showing the dashboard of any user via the "dashboard,$id"
URL syntax.  Since they are all public hyperlinks anyway its not really
a security issue to show our internal database key in the URL.

Signed-off-by: Shawn O. Pearce <sop@google.com>
This commit is contained in:
Shawn O. Pearce
2008-11-26 16:19:10 -08:00
parent 6337849ae5
commit 51dc2c6f06
19 changed files with 347 additions and 126 deletions

View File

@@ -15,7 +15,6 @@
class='com.google.gerrit.server.LoginServlet'/> class='com.google.gerrit.server.LoginServlet'/>
<servlet path='/rpc/AccountService' <servlet path='/rpc/AccountService'
class='com.google.gerrit.server.AccountServiceSrv'/> class='com.google.gerrit.server.AccountServiceSrv'/>
<servlet path='/rpc/ChangeListService' <servlet path='/rpc/ChangeListService'
class='com.google.gerrit.server.ChangeListServiceImpl'/> class='com.google.gerrit.server.ChangeListServiceSrv'/>
</module> </module>

View File

@@ -18,7 +18,9 @@ import com.google.gerrit.client.account.AccountSettings;
import com.google.gerrit.client.changes.ChangeScreen; import com.google.gerrit.client.changes.ChangeScreen;
import com.google.gerrit.client.changes.MineScreen; import com.google.gerrit.client.changes.MineScreen;
import com.google.gerrit.client.changes.MineStarredScreen; import com.google.gerrit.client.changes.MineStarredScreen;
import com.google.gerrit.client.data.ChangeHeader; import com.google.gerrit.client.data.AccountInfo;
import com.google.gerrit.client.data.ChangeInfo;
import com.google.gerrit.client.reviewdb.Account;
import com.google.gwt.user.client.HistoryListener; import com.google.gwt.user.client.HistoryListener;
public class Link implements HistoryListener { public class Link implements HistoryListener {
@@ -35,8 +37,12 @@ public class Link implements HistoryListener {
public static final String ADMIN_GROUPS = "admin,groups"; public static final String ADMIN_GROUPS = "admin,groups";
public static final String ADMIN_PROJECTS = "admin,projects"; public static final String ADMIN_PROJECTS = "admin,projects";
public static String toChange(final ChangeHeader c) { public static String toChange(final ChangeInfo c) {
return "change," + c.id; return "change," + c.getId().get();
}
public static String toAccountDashboard(final AccountInfo acct) {
return "dashboard," + acct.getId().get();
} }
public void onHistoryChanged(final String token) { public void onHistoryChanged(final String token) {
@@ -66,6 +72,11 @@ public class Link implements HistoryListener {
return new ChangeScreen(Integer.parseInt(id)); return new ChangeScreen(Integer.parseInt(id));
} }
else if (token.matches("^dashboard,\\d+$")) {
final String id = token.substring("dashboard,".length());
return new MineScreen(new Account.Id(Integer.parseInt(id)));
}
return null; return null;
} }
} }

View File

@@ -16,16 +16,16 @@ package com.google.gerrit.client.changes;
import com.google.gerrit.client.Gerrit; import com.google.gerrit.client.Gerrit;
import com.google.gerrit.client.Link; import com.google.gerrit.client.Link;
import com.google.gerrit.client.data.ChangeHeader; import com.google.gerrit.client.data.ChangeInfo;
import com.google.gwt.user.client.DOM; import com.google.gwt.user.client.DOM;
import com.google.gwt.user.client.Event; import com.google.gwt.user.client.Event;
import com.google.gwt.user.client.History; import com.google.gwt.user.client.History;
import com.google.gwt.user.client.ui.Hyperlink; import com.google.gwt.user.client.ui.Hyperlink;
public class ChangeLink extends Hyperlink { public class ChangeLink extends Hyperlink {
private ChangeHeader change; private ChangeInfo change;
public ChangeLink(final String text, final ChangeHeader c) { public ChangeLink(final String text, final ChangeInfo c) {
super(text, Link.toChange(c)); super(text, Link.toChange(c));
change = c; change = c;
} }

View File

@@ -12,11 +12,13 @@
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
package com.google.gerrit.client.data; package com.google.gerrit.client.changes;
import com.google.gerrit.client.data.AccountDashboardInfo;
import com.google.gerrit.client.reviewdb.Account;
import com.google.gwt.user.client.rpc.AsyncCallback; import com.google.gwt.user.client.rpc.AsyncCallback;
import com.google.gwtjsonrpc.client.RemoteJsonService; import com.google.gwtjsonrpc.client.RemoteJsonService;
public interface ChangeListService extends RemoteJsonService { public interface ChangeListService extends RemoteJsonService {
public void mine(AsyncCallback<MineResult> callback); void forAccount(Account.Id id, AsyncCallback<AccountDashboardInfo> callback);
} }

View File

@@ -0,0 +1,82 @@
package com.google.gerrit.client.changes;
import com.google.gerrit.client.Gerrit;
import com.google.gerrit.client.data.AccountCache;
import com.google.gerrit.client.data.AccountDashboardInfo;
import com.google.gerrit.client.data.AccountInfo;
import com.google.gerrit.client.data.ChangeInfo;
import com.google.gerrit.client.reviewdb.Account;
import com.google.gerrit.client.reviewdb.Change;
import com.google.gerrit.client.reviewdb.ChangeAccess;
import com.google.gerrit.client.reviewdb.ReviewDb;
import com.google.gwt.user.client.rpc.AsyncCallback;
import com.google.gwtjsonrpc.client.CookieAccess;
import com.google.gwtorm.client.OrmException;
import com.google.gwtorm.client.ResultSet;
import com.google.gwtorm.client.SchemaFactory;
import java.util.ArrayList;
import java.util.List;
public class ChangeListServiceImpl implements ChangeListService {
private final SchemaFactory<ReviewDb> schema;
public ChangeListServiceImpl(final SchemaFactory<ReviewDb> rdf) {
schema = rdf;
}
public void forAccount(Account.Id id,
AsyncCallback<AccountDashboardInfo> callback) {
if (id == null) {
id = idFromCookie();
}
if (id == null) {
callback.onFailure(new IllegalArgumentException("No Account.Id"));
return;
}
try {
final ReviewDb db = schema.open();
try {
final AccountCache accts = new AccountCache(db);
final Account user = accts.get(id);
if (user == null) {
callback.onFailure(new IllegalArgumentException("No such user"));
return;
}
final ChangeAccess changes = db.changes();
final AccountDashboardInfo d;
d = new AccountDashboardInfo(new AccountInfo(user));
d.setByOwner(toInfoList(changes.byOwnerOpen(user.getId()), accts));
d.setClosed(toInfoList(changes.byOwnerMerged(user.getId()), accts));
callback.onSuccess(d);
} finally {
db.close();
}
} catch (OrmException e) {
callback.onFailure(e);
}
}
private List<ChangeInfo> toInfoList(final ResultSet<Change> rs,
final AccountCache accts) throws OrmException {
final ArrayList<ChangeInfo> r = new ArrayList<ChangeInfo>();
for (final Change c : rs) {
r.add(new ChangeInfo(c, accts));
}
return r;
}
private static Account.Id idFromCookie() {
final String myid = CookieAccess.getTokenText(Gerrit.ACCOUNT_COOKIE);
if (myid != null && myid.length() > 0) {
try {
return new Account.Id(Integer.parseInt(myid));
} catch (NumberFormatException e) {
}
}
return null;
}
}

View File

@@ -15,7 +15,7 @@
package com.google.gerrit.client.changes; package com.google.gerrit.client.changes;
import com.google.gerrit.client.Screen; import com.google.gerrit.client.Screen;
import com.google.gerrit.client.data.ChangeHeader; import com.google.gerrit.client.data.ChangeInfo;
public class ChangeScreen extends Screen { public class ChangeScreen extends Screen {
@@ -23,7 +23,7 @@ public class ChangeScreen extends Screen {
super("Loading Change " + id); super("Loading Change " + id);
} }
public ChangeScreen(final ChangeHeader c) { public ChangeScreen(final ChangeInfo c) {
super(c.subject); super(c.getSubject());
} }
} }

View File

@@ -14,8 +14,10 @@
package com.google.gerrit.client.changes; package com.google.gerrit.client.changes;
import com.google.gerrit.client.data.ChangeHeader; import com.google.gerrit.client.Link;
import com.google.gerrit.client.data.ChangeInfo;
import com.google.gwt.user.client.ui.FlexTable; import com.google.gwt.user.client.ui.FlexTable;
import com.google.gwt.user.client.ui.Hyperlink;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
@@ -62,19 +64,20 @@ public class ChangeTable extends FlexTable {
setStyleName(row, C_ID, "gerrit-ChangeTable-ColumnID"); setStyleName(row, C_ID, "gerrit-ChangeTable-ColumnID");
} }
private void populateChangeRow(final int row, final ChangeHeader c) { private void populateChangeRow(final int row, final ChangeInfo c) {
setWidget(row, C_ID, new ChangeLink(String.valueOf(c.id), c)); setWidget(row, C_ID, new ChangeLink(String.valueOf(c.getId().get()), c));
String s = c.subject; String s = c.getSubject();
if (c.status != null) { if (c.getStatus() != null) {
s += " (" + c.status + ")"; s += " (" + c.getStatus().name() + ")";
} }
setWidget(row, C_SUBJECT, new ChangeLink(s, c)); setWidget(row, C_SUBJECT, new ChangeLink(s, c));
setText(row, C_OWNER, c.owner.fullName); setWidget(row, C_OWNER, new Hyperlink(c.getOwner().getFullName(), Link
.toAccountDashboard(c.getOwner())));
setText(row, C_REVIEWERS, "TODO"); setText(row, C_REVIEWERS, "TODO");
setText(row, C_PROJECT, c.project.name); setText(row, C_PROJECT, c.getProject().getName());
setText(row, C_LAST_UPDATE, c.lastUpdate.toString()); setText(row, C_LAST_UPDATE, "TODO");
} }
private void setStyleName(final int row, final int col, final String name) { private void setStyleName(final int row, final int col, final String name) {
@@ -142,7 +145,7 @@ public class ChangeTable extends FlexTable {
this.titleText = titleText; this.titleText = titleText;
} }
public void display(final List<ChangeHeader> changeList) { public void display(final List<ChangeInfo> changeList) {
final int sz = changeList != null ? changeList.size() : 0; final int sz = changeList != null ? changeList.size() : 0;
final boolean hadData = rows > 0; final boolean hadData = rows > 0;

View File

@@ -15,30 +15,35 @@
package com.google.gerrit.client.changes; package com.google.gerrit.client.changes;
import com.google.gerrit.client.Screen; import com.google.gerrit.client.Screen;
import com.google.gerrit.client.data.MineResult; import com.google.gerrit.client.data.AccountDashboardInfo;
import com.google.gerrit.client.reviewdb.Account;
import com.google.gwt.core.client.GWT; import com.google.gwt.core.client.GWT;
import com.google.gwt.user.client.rpc.AsyncCallback; import com.google.gwt.user.client.rpc.AsyncCallback;
public class MineScreen extends Screen { public class MineScreen extends Screen {
private ChangeTable table; private ChangeTable table;
private ChangeTable.Section byMe; private ChangeTable.Section byOwner;
private ChangeTable.Section forReview; private ChangeTable.Section forReview;
private ChangeTable.Section closed; private ChangeTable.Section closed;
public MineScreen() { public MineScreen() {
this(null);
}
public MineScreen(final Account.Id id) {
super(Util.C.mineHeading()); super(Util.C.mineHeading());
table = new ChangeTable(); table = new ChangeTable();
byMe = new ChangeTable.Section(Util.C.mineByMe()); byOwner = new ChangeTable.Section(Util.C.mineByMe());
forReview = new ChangeTable.Section(Util.C.mineForReview()); forReview = new ChangeTable.Section(Util.C.mineForReview());
closed = new ChangeTable.Section(Util.C.mineClosed()); closed = new ChangeTable.Section(Util.C.mineClosed());
Util.LIST_SVC.mine(new AsyncCallback<MineResult>() { Util.LIST_SVC.forAccount(id, new AsyncCallback<AccountDashboardInfo>() {
public void onSuccess(final MineResult r) { public void onSuccess(final AccountDashboardInfo r) {
byMe.display(r.byMe); byOwner.display(r.getByOwner());
forReview.display(r.forReview); forReview.display(r.getForReview());
closed.display(r.closed); closed.display(r.getClosed());
} }
public void onFailure(final Throwable caught) { public void onFailure(final Throwable caught) {
@@ -46,7 +51,7 @@ public class MineScreen extends Screen {
} }
}); });
table.addSection(byMe); table.addSection(byOwner);
table.addSection(forReview); table.addSection(forReview);
table.addSection(closed); table.addSection(closed);

View File

@@ -14,7 +14,6 @@
package com.google.gerrit.client.changes; package com.google.gerrit.client.changes;
import com.google.gerrit.client.data.ChangeListService;
import com.google.gwt.core.client.GWT; import com.google.gwt.core.client.GWT;
import com.google.gwt.user.client.rpc.ServiceDefTarget; import com.google.gwt.user.client.rpc.ServiceDefTarget;

View File

@@ -0,0 +1,26 @@
package com.google.gerrit.client.data;
import com.google.gerrit.client.reviewdb.Account;
import com.google.gerrit.client.reviewdb.ReviewDb;
import com.google.gwtorm.client.OrmException;
import java.util.HashMap;
public class AccountCache {
private final ReviewDb db;
private final HashMap<Account.Id, Account> cache;
public AccountCache(final ReviewDb schema) {
db = schema;
cache = new HashMap<Account.Id, Account>();
}
public Account get(final Account.Id id) throws OrmException {
Account a = cache.get(id);
if (a == null) {
a = db.accounts().byId(id);
cache.put(id, a);
}
return a;
}
}

View File

@@ -0,0 +1,59 @@
// Copyright 2008 Google Inc.
//
// 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.data;
import java.util.List;
public class AccountDashboardInfo {
protected AccountInfo owner;
protected List<ChangeInfo> byOwner;
protected List<ChangeInfo> forReview;
protected List<ChangeInfo> closed;
protected AccountDashboardInfo() {
}
public AccountDashboardInfo(final AccountInfo forUser) {
owner = forUser;
}
public AccountInfo getOwner() {
return owner;
}
public List<ChangeInfo> getByOwner() {
return byOwner;
}
public void setByOwner(List<ChangeInfo> c) {
byOwner = c;
}
public List<ChangeInfo> getForReview() {
return forReview;
}
public void setForReview(List<ChangeInfo> c) {
forReview = c;
}
public List<ChangeInfo> getClosed() {
return closed;
}
public void setClosed(List<ChangeInfo> c) {
closed = c;
}
}

View File

@@ -14,6 +14,31 @@
package com.google.gerrit.client.data; package com.google.gerrit.client.data;
public class ProjectIdentity { import com.google.gerrit.client.reviewdb.Account;
public String name;
public class AccountInfo {
protected Account.Id id;
protected String fullName;
protected String preferredEmail;
protected AccountInfo() {
}
public AccountInfo(final Account a) {
id = a.getId();
fullName = a.getFullName();
preferredEmail = a.getPreferredEmail();
}
public Account.Id getId() {
return id;
}
public String getFullName() {
return fullName;
}
public String getPreferredEmail() {
return preferredEmail;
}
} }

View File

@@ -0,0 +1,58 @@
// Copyright 2008 Google Inc.
//
// 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.data;
import com.google.gerrit.client.reviewdb.Change;
import com.google.gwtorm.client.OrmException;
public class ChangeInfo {
protected Change.Id id;
protected String subject;
protected Change.Status status;
protected AccountInfo owner;
protected ProjectInfo project;
protected ChangeInfo() {
}
public ChangeInfo(final Change c, final AccountCache accounts)
throws OrmException {
id = c.getKey();
subject = c.getSubject();
status = c.getStatus();
owner = new AccountInfo(accounts.get(c.getOwner()));
project = new ProjectInfo(c.getDest().getParentKey());
}
public Change.Id getId() {
return id;
}
public String getSubject() {
return subject;
}
public Change.Status getStatus() {
return status;
}
public AccountInfo getOwner() {
return owner;
}
public ProjectInfo getProject() {
return project;
}
}

View File

@@ -14,15 +14,19 @@
package com.google.gerrit.client.data; package com.google.gerrit.client.data;
import java.util.Date; import com.google.gerrit.client.reviewdb.Project;
import java.util.List;
public class ChangeHeader { public class ProjectInfo {
public int id; protected Project.NameKey key;
public String subject;
public String status; protected ProjectInfo() {
public UserIdentity owner; }
public List<UserIdentity> reviewers;
public ProjectIdentity project; public ProjectInfo(final Project.NameKey key) {
public Date lastUpdate; this.key = key;
}
public String getName() {
return key.get();
}
} }

View File

@@ -1,20 +0,0 @@
// Copyright 2008 Google Inc.
//
// 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.data;
public class UserIdentity {
public String fullName;
public String emailAddress;
}

View File

@@ -39,10 +39,13 @@ public final class Change {
} }
} }
public static enum Status { protected static final char STATUS_NEW = 'n';
NEW('n'), protected static final char STATUS_MERGED = 'M';
MERGED('M'), public static enum Status {
NEW(STATUS_NEW),
MERGED(STATUS_MERGED),
ABANDONED('A'); ABANDONED('A');
@@ -134,6 +137,10 @@ public final class Change {
return dest; return dest;
} }
public String getSubject() {
return subject;
}
/** Get the id of the most current {@link PatchSet} in this change. */ /** Get the id of the most current {@link PatchSet} in this change. */
public PatchSet.Id currentPatchSetId() { public PatchSet.Id currentPatchSetId() {
if (currentPatchSetId > 0) { if (currentPatchSetId > 0) {

View File

@@ -17,8 +17,18 @@ package com.google.gerrit.client.reviewdb;
import com.google.gwtorm.client.Access; import com.google.gwtorm.client.Access;
import com.google.gwtorm.client.OrmException; import com.google.gwtorm.client.OrmException;
import com.google.gwtorm.client.PrimaryKey; import com.google.gwtorm.client.PrimaryKey;
import com.google.gwtorm.client.Query;
import com.google.gwtorm.client.ResultSet;
public interface ChangeAccess extends Access<Change, Change.Id> { public interface ChangeAccess extends Access<Change, Change.Id> {
@PrimaryKey("changeId") @PrimaryKey("changeId")
Change get(Change.Id id) throws OrmException; Change get(Change.Id id) throws OrmException;
@Query("WHERE owner = ? AND status = '" + Change.STATUS_NEW
+ "' ORDER BY createdOn DESC")
ResultSet<Change> byOwnerOpen(Account.Id id) throws OrmException;
@Query("WHERE owner = ? AND status = '" + Change.STATUS_MERGED
+ "' ORDER BY createdOn DESC LIMIT 20")
ResultSet<Change> byOwnerMerged(Account.Id id) throws OrmException;
} }

View File

@@ -1,51 +0,0 @@
// Copyright 2008 Google Inc.
//
// 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.server;
import com.google.gerrit.client.data.ChangeHeader;
import com.google.gerrit.client.data.ChangeListService;
import com.google.gerrit.client.data.MineResult;
import com.google.gerrit.client.data.ProjectIdentity;
import com.google.gerrit.client.data.UserIdentity;
import com.google.gwt.user.client.rpc.AsyncCallback;
import java.util.ArrayList;
import java.util.Date;
public class ChangeListServiceImpl extends GerritJsonServlet implements ChangeListService {
@Override
protected Object createServiceHandle() throws Exception {
return this;
}
public void mine(final AsyncCallback<MineResult> callback) {
final MineResult r = new MineResult();
r.byMe = new ArrayList<ChangeHeader>();
for (int i = 10; i < 10 + 2; i++) {
final ChangeHeader c = new ChangeHeader();
c.id = i;
c.subject = "Change " + i;
c.owner = new UserIdentity();
c.owner.fullName = "User " + i;
c.project = new ProjectIdentity();
c.project.name = "platform/test";
c.lastUpdate = new Date();
r.byMe.add(c);
}
callback.onSuccess(r);
}
}

View File

@@ -12,12 +12,14 @@
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
package com.google.gerrit.client.data; package com.google.gerrit.server;
import java.util.List; import com.google.gerrit.client.changes.ChangeListServiceImpl;
public class MineResult { /** Publishes {@link ChangeListServiceImpl} over JSON. */
public List<ChangeHeader> byMe; public class ChangeListServiceSrv extends GerritJsonServlet {
public List<ChangeHeader> forReview; @Override
public List<ChangeHeader> closed; protected Object createServiceHandle() throws Exception {
return new ChangeListServiceImpl(GerritServer.getInstance().getDatabase());
}
} }