Always publish comments on all revisions from the reply box

Fetch comments for all revisions when populating the history table, so
it shows what was published. This actually allows us to simplify the
history table considerably, since we don't need to lazily fetch
comments for different patch set IDs. For this to work we need to move
the History population up one level of callbacks, which is doable
because it doesn't actually depend on the revision callback returning.

Instead of resorting comments by line, trust the order that comes back
from the handler.

Bug: issue 1100
Change-Id: I2749d9f693adca22855958208d810e0231bb3325
This commit is contained in:
Dave Borowitz 2015-04-30 10:07:29 -07:00
parent 8a1da03928
commit 5fd7813e72
10 changed files with 95 additions and 148 deletions

View File

@ -908,16 +908,19 @@ public class ChangeScreen extends Screen {
}
private List<NativeMap<JsArray<CommentInfo>>> loadComments(
RevisionInfo rev, CallbackGroup group) {
final int id = rev._number();
final RevisionInfo rev, CallbackGroup group) {
final List<NativeMap<JsArray<CommentInfo>>> r = new ArrayList<>(1);
ChangeApi.revision(changeId.get(), rev.name())
.view("comments")
// TODO(dborowitz): Could eliminate this call by adding an option to include
// inline comments in the change detail.
ChangeApi.comments(changeId.get())
.get(group.add(new AsyncCallback<NativeMap<JsArray<CommentInfo>>>() {
@Override
public void onSuccess(NativeMap<JsArray<CommentInfo>> result) {
r.add(result);
history.addComments(id, result);
// Return value is used for populating the file table, so only count
// comments for the current revision. Still include all comments in
// the history table.
r.add(filterForRevision(result, rev._number()));
history.addComments(result);
}
@Override
@ -927,6 +930,23 @@ public class ChangeScreen extends Screen {
return r;
}
private static NativeMap<JsArray<CommentInfo>> filterForRevision(
NativeMap<JsArray<CommentInfo>> comments, int id) {
NativeMap<JsArray<CommentInfo>> filtered = NativeMap.create();
for (String k : comments.keySet()) {
JsArray<CommentInfo> allRevisions = comments.get(k);
JsArray<CommentInfo> thisRevision = JsArray.createArray().cast();
for (int i = 0; i < allRevisions.length(); i++) {
CommentInfo c = allRevisions.get(i);
if (c.patch_set() == id) {
thisRevision.push(c);
}
}
filtered.put(k, thisRevision);
}
return filtered;
}
private List<NativeMap<JsArray<CommentInfo>>> loadDrafts(
RevisionInfo rev, CallbackGroup group) {
final List<NativeMap<JsArray<CommentInfo>>> r = new ArrayList<>(1);
@ -1129,10 +1149,7 @@ public class ChangeScreen extends Screen {
initRevisionsAction(info, revision, emptyMap);
quickApprove.setVisible(false);
actions.reloadRevisionActions(emptyMap);
}
private void renderRevisionInfo(ChangeInfo info,
NativeMap<ActionInfo> actionMap) {
RevisionInfo revisionInfo = info.revision(revision);
boolean current = revision.equals(info.current_revision())
&& !revisionInfo.is_edit();
@ -1146,8 +1163,6 @@ public class ChangeScreen extends Screen {
statusText.setInnerText(Util.toLongString(info.status()));
}
initRevisionsAction(info, revision, actionMap);
if (Gerrit.isSignedIn()) {
replyAction = new ReplyAction(info, revision, hasDraftComments,
style, commentLinkProcessor, reply, quickApprove);
@ -1160,6 +1175,11 @@ public class ChangeScreen extends Screen {
} else {
quickApprove.setVisible(false);
}
}
private void renderRevisionInfo(ChangeInfo info,
NativeMap<ActionInfo> actionMap) {
initRevisionsAction(info, revision, actionMap);
actions.reloadRevisionActions(actionMap);
}

View File

@ -26,8 +26,6 @@ import com.google.gwt.user.client.ui.Composite;
import com.google.gwt.user.client.ui.FlowPanel;
import com.google.gwt.user.client.ui.HTMLPanel;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
class FileComments extends Composite {
@ -38,22 +36,15 @@ class FileComments extends Composite {
@UiField FlowPanel comments;
FileComments(CommentLinkProcessor clp,
PatchSet.Id ps,
PatchSet.Id defaultPs,
String title,
List<CommentInfo> list) {
initWidget(uiBinder.createAndBindUi(this));
path.setTargetHistoryToken(url(ps, list.get(0)));
path.setTargetHistoryToken(url(defaultPs, list.get(0)));
path.setText(title);
Collections.sort(list, new Comparator<CommentInfo>() {
@Override
public int compare(CommentInfo a, CommentInfo b) {
return a.line() - b.line();
}
});
for (CommentInfo c : list) {
comments.add(new LineComment(clp, ps, c));
comments.add(new LineComment(clp, defaultPs, c));
}
}

View File

@ -14,8 +14,6 @@
package com.google.gerrit.client.change;
import com.google.gerrit.client.account.AccountInfo;
import com.google.gerrit.client.changes.ChangeApi;
import com.google.gerrit.client.changes.ChangeInfo;
import com.google.gerrit.client.changes.ChangeInfo.MessageInfo;
import com.google.gerrit.client.changes.CommentInfo;
@ -23,9 +21,7 @@ import com.google.gerrit.client.rpc.NativeMap;
import com.google.gerrit.client.rpc.Natives;
import com.google.gerrit.client.ui.CommentLinkProcessor;
import com.google.gerrit.reviewdb.client.Change;
import com.google.gerrit.reviewdb.client.PatchSet;
import com.google.gwt.core.client.JsArray;
import com.google.gwt.user.client.rpc.AsyncCallback;
import com.google.gwt.user.client.ui.FlowPanel;
import com.google.gwt.user.client.ui.Widget;
@ -33,22 +29,15 @@ import java.sql.Timestamp;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
class History extends FlowPanel {
private CommentLinkProcessor clp;
private ReplyAction replyAction;
private Change.Id changeId;
private final Set<Integer> loaded = new HashSet<>();
private final Map<AuthorRevision, List<CommentInfo>> byAuthor =
new HashMap<>();
private final List<Integer> toLoad = new ArrayList<>(4);
private int active;
private final Map<Integer, List<CommentInfo>> byAuthor = new HashMap<>();
void set(CommentLinkProcessor clp, ReplyAction ra,
Change.Id id, ChangeInfo info) {
@ -60,9 +49,7 @@ class History extends FlowPanel {
if (messages != null) {
for (MessageInfo msg : Natives.asList(messages)) {
Message ui = new Message(this, msg);
if (loaded.contains(msg._revisionNumber())) {
ui.addComments(comments(msg));
}
ui.addComments(comments(msg));
add(ui);
}
autoOpen(ChangeScreen.myLastReply(info));
@ -99,18 +86,16 @@ class History extends FlowPanel {
replyAction.onReply(info);
}
void addComments(int id, NativeMap<JsArray<CommentInfo>> map) {
loaded.add(id);
void addComments(NativeMap<JsArray<CommentInfo>> map) {
for (String path : map.keySet()) {
for (CommentInfo c : Natives.asList(map.get(path))) {
c.path(path);
if (c.author() != null) {
AuthorRevision k = new AuthorRevision(c.author(), id);
List<CommentInfo> l = byAuthor.get(k);
int authorId = c.author()._account_id();
List<CommentInfo> l = byAuthor.get(authorId);
if (l == null) {
l = new ArrayList<>();
byAuthor.put(k, l);
byAuthor.put(authorId, l);
}
l.add(c);
}
@ -118,58 +103,13 @@ class History extends FlowPanel {
}
}
void load(int revisionNumber) {
if (revisionNumber > 0 && loaded.add(revisionNumber)) {
toLoad.add(revisionNumber);
start();
}
}
private void start() {
if (active >= 2 || toLoad.isEmpty() || !isAttached()) {
return;
}
final int revisionNumber = toLoad.remove(0);
active++;
ChangeApi.revision(new PatchSet.Id(changeId, revisionNumber))
.view("comments")
.get(new AsyncCallback<NativeMap<JsArray<CommentInfo>>>() {
@Override
public void onSuccess(NativeMap<JsArray<CommentInfo>> result) {
addComments(revisionNumber, result);
update(revisionNumber);
--active;
start();
}
@Override
public void onFailure(Throwable caught) {
loaded.remove(revisionNumber);
loaded.removeAll(toLoad);
toLoad.clear();
active--;
}
});
}
private void update(int revisionNumber) {
for (Widget child : getChildren()) {
Message ui = (Message) child;
MessageInfo info = ui.getMessageInfo();
if (info._revisionNumber() == revisionNumber) {
ui.addComments(comments(info));
}
}
}
private List<CommentInfo> comments(MessageInfo msg) {
if (msg.author() == null) {
return Collections.emptyList();
}
AuthorRevision k = new AuthorRevision(msg.author(), msg._revisionNumber());
List<CommentInfo> list = byAuthor.get(k);
int authorId = msg.author()._account_id();
List<CommentInfo> list = byAuthor.get(authorId);
if (list == null) {
return Collections.emptyList();
}
@ -187,34 +127,10 @@ class History extends FlowPanel {
if (match.isEmpty()) {
return Collections.emptyList();
} else if (other.isEmpty()) {
byAuthor.remove(k);
byAuthor.remove(authorId);
} else {
byAuthor.put(k, other);
byAuthor.put(authorId, other);
}
return match;
}
private static final class AuthorRevision {
final int author;
final int revision;
AuthorRevision(AccountInfo author, int revision) {
this.author = author._account_id();
this.revision = revision;
}
@Override
public int hashCode() {
return author * 31 + revision;
}
@Override
public boolean equals(Object o) {
if (!(o instanceof AuthorRevision)) {
return false;
}
AuthorRevision b = (AuthorRevision) o;
return author == b.author && revision == b.revision;
}
}
}

View File

@ -33,14 +33,29 @@ class LineComment extends Composite {
interface Binder extends UiBinder<HTMLPanel, LineComment> {}
private static final Binder uiBinder = GWT.create(Binder.class);
@UiField Element psLoc;
@UiField Element psNum;
@UiField Element fileLoc;
@UiField Element lineLoc;
@UiField InlineHyperlink line;
@UiField Element message;
LineComment(CommentLinkProcessor clp, PatchSet.Id ps, CommentInfo info) {
LineComment(CommentLinkProcessor clp,
PatchSet.Id defaultPs,
CommentInfo info) {
initWidget(uiBinder.createAndBindUi(this));
PatchSet.Id ps;
if (info.patch_set() != defaultPs.get()) {
ps = new PatchSet.Id(defaultPs.getParentKey(), info.patch_set());
psNum.setInnerText(Integer.toString(info.patch_set()));
} else {
ps = defaultPs;
psLoc.removeFromParent();
psLoc = null;
psNum= null;
}
if (info.has_line()) {
fileLoc.removeFromParent();
fileLoc = null;

View File

@ -29,13 +29,16 @@ limitations under the License.
font-weight: bold;
}
.message {
margin-left: 111px;
margin-left: 135px;
}
</ui:style>
<g:HTMLPanel styleName='{style.box}'>
<div class='{style.location}' ui:field='fileLoc'><ui:msg>File Comment</ui:msg></div>
<div class='{style.location}' ui:field='lineLoc'><ui:msg>Line <c:InlineHyperlink ui:field='line'/>:</ui:msg></div>
<div class='{style.location}'>
<span ui:field='psLoc'><ui:msg>PS<span ui:field='psNum'/>, </ui:msg></span>
<span ui:field='fileLoc'><ui:msg>File Comment</ui:msg></span>
<span ui:field='lineLoc'><ui:msg>Line <c:InlineHyperlink ui:field='line'/>:</ui:msg></span>
</div>
<div class='{style.message}' ui:field='message'/>
</g:HTMLPanel>
</ui:UiBinder>

View File

@ -121,13 +121,9 @@ class Message extends Composite {
}
void setOpen(boolean open) {
if (open && info._revisionNumber() > 0) {
if (commentList == null) {
history.load(info._revisionNumber());
} else if (!commentList.isEmpty()) {
renderComments(commentList);
commentList = Collections.emptyList();
}
if (open && info._revisionNumber() > 0 && !commentList.isEmpty()) {
renderComments(commentList);
commentList = Collections.emptyList();
}
setName(open);
@ -156,7 +152,6 @@ class Message extends Composite {
void autoOpen() {
if (commentList == null) {
autoOpen = true;
history.load(info._revisionNumber());
} else if (!commentList.isEmpty()) {
setOpen(true);
}

View File

@ -21,9 +21,9 @@ import com.google.gerrit.client.changes.ChangeApi;
import com.google.gerrit.client.changes.ChangeInfo.ApprovalInfo;
import com.google.gerrit.client.changes.ChangeInfo.LabelInfo;
import com.google.gerrit.client.changes.ChangeInfo.MessageInfo;
import com.google.gerrit.client.changes.CommentApi;
import com.google.gerrit.client.changes.CommentInfo;
import com.google.gerrit.client.changes.ReviewInput;
import com.google.gerrit.client.changes.ReviewInput.DraftHandling;
import com.google.gerrit.client.changes.Util;
import com.google.gerrit.client.rpc.GerritCallback;
import com.google.gerrit.client.rpc.NativeMap;
@ -140,19 +140,19 @@ class ReplyBox extends Composite {
protected void onLoad() {
commentsPanel.setVisible(false);
post.setEnabled(false);
CommentApi.drafts(psId, new AsyncCallback<NativeMap<JsArray<CommentInfo>>>() {
@Override
public void onSuccess(NativeMap<JsArray<CommentInfo>> result) {
attachComments(result);
displayComments(result);
post.setEnabled(true);
}
ChangeApi.drafts(psId.getParentKey().get())
.get(new AsyncCallback<NativeMap<JsArray<CommentInfo>>>() {
@Override
public void onSuccess(NativeMap<JsArray<CommentInfo>> result) {
displayComments(result);
post.setEnabled(true);
}
@Override
public void onFailure(Throwable caught) {
post.setEnabled(true);
}
});
@Override
public void onFailure(Throwable caught) {
post.setEnabled(true);
}
});
Scheduler.get().scheduleDeferred(new ScheduledCommand() {
@Override
@ -186,6 +186,9 @@ class ReplyBox extends Composite {
private void postReview() {
in.message(message.getText().trim());
// Don't send any comments in the request; just publish everything, even if
// e.g. a draft was modified in another tab since we last looked it up.
in.drafts(DraftHandling.PUBLISH_ALL_REVISIONS);
in.prePost();
ChangeApi.revision(psId.getParentKey().get(), revision)
.view("review")
@ -379,11 +382,6 @@ class ReplyBox extends Composite {
&& values.contains((short) 1);
}
private void attachComments(NativeMap<JsArray<CommentInfo>> result) {
in.drafts(ReviewInput.DraftHandling.KEEP);
in.comments(result);
}
private void displayComments(NativeMap<JsArray<CommentInfo>> m) {
comments.clear();

View File

@ -102,6 +102,14 @@ public class ChangeApi {
return call(id, revision, "actions");
}
public static RestApi comments(int id) {
return call(id, "comments");
}
public static RestApi drafts(int id) {
return call(id, "drafts");
}
public static void edit(int id, AsyncCallback<EditInfo> cb) {
edit(id).get(cb);
}

View File

@ -82,6 +82,7 @@ public class CommentInfo extends JavaScriptObject {
public final native String path() /*-{ return this.path }-*/;
public final native String id() /*-{ return this.id }-*/;
public final native String in_reply_to() /*-{ return this.in_reply_to }-*/;
public final native int patch_set() /*-{ return this.patch_set }-*/;
public final Side side() {
String s = sideRaw();

View File

@ -24,7 +24,7 @@ public class ReviewInput extends JavaScriptObject {
}
public static enum DraftHandling {
DELETE, PUBLISH, KEEP
DELETE, PUBLISH, KEEP, PUBLISH_ALL_REVISIONS
}
public static ReviewInput create() {