Add option to make approved patches be auto-submitted

Add a "Publish & Submit" button to the web review screen.  This
button performs the actions that the "Publish Comments" button
performs while additionally submitting the patch for merging.

Bug: issue 391
Change-Id: If9f3415be1378e9303afe34486dce557a926dc71
diff --git a/gerrit-common/src/main/java/com/google/gerrit/common/data/PatchSetPublishDetail.java b/gerrit-common/src/main/java/com/google/gerrit/common/data/PatchSetPublishDetail.java
index 70cc10d..7a0ada3 100644
--- a/gerrit-common/src/main/java/com/google/gerrit/common/data/PatchSetPublishDetail.java
+++ b/gerrit-common/src/main/java/com/google/gerrit/common/data/PatchSetPublishDetail.java
@@ -32,6 +32,7 @@
   protected List<PatchLineComment> drafts;
   protected Map<ApprovalCategory.Id, Set<ApprovalCategoryValue.Id>> allowed;
   protected Map<ApprovalCategory.Id, PatchSetApproval> given;
+  protected boolean isSubmitAllowed;
 
   public Map<ApprovalCategory.Id, Set<ApprovalCategoryValue.Id>> getAllowed() {
     return allowed;
@@ -66,6 +67,10 @@
     this.drafts = drafts;
   }
 
+  public void setSubmitAllowed(boolean allowed) {
+    isSubmitAllowed = allowed;
+  }
+
   public AccountInfoCache getAccounts() {
     return accounts;
   }
@@ -94,4 +99,8 @@
   public PatchSetApproval getChangeApproval(final ApprovalCategory.Id id) {
     return given.get(id);
   }
+
+  public boolean isSubmitAllowed() {
+    return isSubmitAllowed;
+  }
 }
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeConstants.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeConstants.java
index e79d1a2..fed0927 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeConstants.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeConstants.java
@@ -96,6 +96,7 @@
 
   String buttonReview();
   String buttonPublishCommentsSend();
+  String buttonPublishSubmitSend();
   String buttonPublishCommentsCancel();
   String headingCoverMessage();
   String headingPatchComments();
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeConstants.properties b/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeConstants.properties
index 7c4c986..155d657 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeConstants.properties
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeConstants.properties
@@ -73,6 +73,7 @@
 
 buttonReview = Review
 buttonPublishCommentsSend = Publish Comments
+buttonPublishSubmitSend = Publish and Submit
 buttonPublishCommentsCancel = Cancel
 headingCoverMessage = Cover Message:
 headingPatchComments = Patch Comments:
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/PublishCommentScreen.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/PublishCommentScreen.java
index 9691119..d0d148e 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/PublishCommentScreen.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/PublishCommentScreen.java
@@ -25,6 +25,7 @@
 import com.google.gerrit.client.ui.SmallHeading;
 import com.google.gerrit.common.PageLinks;
 import com.google.gerrit.common.data.ApprovalType;
+import com.google.gerrit.common.data.ChangeDetail;
 import com.google.gerrit.common.data.PatchSetPublishDetail;
 import com.google.gerrit.reviewdb.ApprovalCategory;
 import com.google.gerrit.reviewdb.ApprovalCategoryValue;
@@ -67,6 +68,7 @@
   private NpTextArea message;
   private FlowPanel draftsPanel;
   private Button send;
+  private Button submit;
   private Button cancel;
   private boolean saveStateOnUnload = true;
   private List<CommentEditorPanel> commentEditors;
@@ -103,12 +105,17 @@
     body.add(draftsPanel);
 
     final FlowPanel buttonRow = new FlowPanel();
+    buttonRow.setStyleName(Gerrit.RESOURCES.css().patchSetActions());
     body.add(buttonRow);
 
     send = new Button(Util.C.buttonPublishCommentsSend());
     send.addClickHandler(this);
     buttonRow.add(send);
 
+    submit = new Button(Util.C.buttonPublishSubmitSend());
+    submit.addClickHandler(this);
+    buttonRow.add(submit);
+
     cancel = new Button(Util.C.buttonPublishCommentsCancel());
     cancel.addClickHandler(this);
     buttonRow.add(cancel);
@@ -142,7 +149,9 @@
   public void onClick(final ClickEvent event) {
     final Widget sender = (Widget) event.getSource();
     if (send == sender) {
-      onSend();
+      onSend(false);
+    } else if (submit == sender) {
+      onSend(true);
     } else if (cancel == sender) {
       saveStateOnUnload = false;
       goChange();
@@ -250,6 +259,7 @@
     if (r.getChange().getStatus().isOpen()) {
       initApprovals(r, approvalPanel);
     }
+
     if (lastState != null && patchSetId.equals(lastState.patchSetId)) {
       message.setText(lastState.message);
     }
@@ -285,11 +295,13 @@
         panel.add(editor);
       }
     }
+
+    submit.setVisible(r.isSubmitAllowed());
   }
 
-  private void onSend() {
+  private void onSend(final boolean submit) {
     if (commentEditors.isEmpty()) {
-      onSend2();
+      onSend2(submit);
     } else {
       final GerritCallback<VoidResult> afterSaveDraft =
           new GerritCallback<VoidResult>() {
@@ -298,7 +310,7 @@
             @Override
             public void onSuccess(final VoidResult result) {
               if (++done == commentEditors.size()) {
-                onSend2();
+                onSend2(submit);
               }
             }
           };
@@ -308,7 +320,7 @@
     }
   }
 
-  private void onSend2() {
+  private void onSend2(final boolean submit) {
     final Map<ApprovalCategory.Id, ApprovalCategoryValue.Id> values =
         new HashMap<ApprovalCategory.Id, ApprovalCategoryValue.Id>();
     for (final ValueRadioButton b : approvalButtons) {
@@ -321,6 +333,20 @@
         new HashSet<ApprovalCategoryValue.Id>(values.values()),
         new GerritCallback<VoidResult>() {
           public void onSuccess(final VoidResult result) {
+            if(submit) {
+              submit();
+            } else {
+              saveStateOnUnload = false;
+              goChange();
+            }
+          }
+        });
+  }
+
+  private void submit() {
+    Util.MANAGE_SVC.submit(patchSetId,
+        new GerritCallback<ChangeDetail>() {
+          public void onSuccess(ChangeDetail result) {
             saveStateOnUnload = false;
             goChange();
           }
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/changedetail/PatchSetPublishDetailFactory.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/changedetail/PatchSetPublishDetailFactory.java
index c98d129..3d201db 100644
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/changedetail/PatchSetPublishDetailFactory.java
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/changedetail/PatchSetPublishDetailFactory.java
@@ -33,6 +33,7 @@
 import com.google.gerrit.server.account.AccountInfoCacheFactory;
 import com.google.gerrit.server.patch.PatchSetInfoFactory;
 import com.google.gerrit.server.patch.PatchSetInfoNotAvailableException;
+import com.google.gerrit.server.project.CanSubmitResult;
 import com.google.gerrit.server.project.ChangeControl;
 import com.google.gerrit.server.project.NoSuchChangeException;
 import com.google.gerrit.server.project.ProjectCache;
@@ -122,6 +123,9 @@
     detail.setAllowed(allowed);
     detail.setGiven(given);
 
+    final CanSubmitResult canSubmitResult = control.canSubmit(patchSetId);
+    detail.setSubmitAllowed(canSubmitResult == CanSubmitResult.OK);
+
     return detail;
   }
 
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/project/ChangeControl.java b/gerrit-server/src/main/java/com/google/gerrit/server/project/ChangeControl.java
index 1664178..0ee2c9e 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/project/ChangeControl.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/project/ChangeControl.java
@@ -185,11 +185,7 @@
   }
 
   /** @return {@link CanSubmitResult#OK}, or a result with an error message. */
-  public CanSubmitResult canSubmit(final PatchSet.Id patchSetId, final ReviewDb db,
-        final ApprovalTypes approvalTypes,
-        FunctionState.Factory functionStateFactory)
-         throws OrmException {
-
+  public CanSubmitResult canSubmit(final PatchSet.Id patchSetId) {
     if (change.getStatus().isClosed()) {
       return new CanSubmitResult("Change " + change.getId() + " is closed");
     }
@@ -202,6 +198,19 @@
     if (!(getCurrentUser() instanceof IdentifiedUser)) {
       return new CanSubmitResult("User is not signed-in");
     }
+    return CanSubmitResult.OK;
+  }
+
+  /** @return {@link CanSubmitResult#OK}, or a result with an error message. */
+  public CanSubmitResult canSubmit(final PatchSet.Id patchSetId, final ReviewDb db,
+        final ApprovalTypes approvalTypes,
+        FunctionState.Factory functionStateFactory)
+         throws OrmException {
+
+    CanSubmitResult result = canSubmit(patchSetId);
+    if (result != CanSubmitResult.OK) {
+      return result;
+    }
 
     final List<PatchSetApproval> allApprovals =
         new ArrayList<PatchSetApproval>(db.patchSetApprovals().byPatchSet(