Add API for work in progress workflow

Change-Id: Idb0faa778977038cea336da38acdfb4089679618
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/api/change/ChangeIT.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/api/change/ChangeIT.java
index 498286c..0456aa1 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/api/change/ChangeIT.java
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/api/change/ChangeIT.java
@@ -256,6 +256,67 @@
   }
 
   @Test
+  public void setWorkInProgressNotAllowedWithoutPermission() throws Exception {
+    PushOneCommit.Result rwip = createChange();
+    String changeId = rwip.getChangeId();
+
+    setApiUser(user);
+    exception.expect(AuthException.class);
+    exception.expectMessage("not allowed to set work in progress");
+    gApi.changes().id(changeId).setWorkInProgress();
+  }
+
+  @Test
+  public void setReadyForReviewNotAllowedWithoutPermission() throws Exception {
+    PushOneCommit.Result rready = createChange();
+    String changeId = rready.getChangeId();
+    gApi.changes().id(changeId).setWorkInProgress();
+
+    setApiUser(user);
+    exception.expect(AuthException.class);
+    exception.expectMessage("not allowed to set ready for review");
+    gApi.changes().id(changeId).setReadyForReview();
+  }
+
+  @Test
+  public void toggleWorkInProgressState() throws Exception {
+    PushOneCommit.Result r = createChange();
+    String changeId = r.getChangeId();
+
+    // With message
+    gApi.changes().id(changeId).setWorkInProgress("Needs some refactoring");
+
+    ChangeInfo info = gApi.changes().id(changeId).get();
+
+    assertThat(info.workInProgress).isTrue();
+    assertThat(Iterables.getLast(info.messages).message).contains("Needs some refactoring");
+    assertThat(Iterables.getLast(info.messages).tag).contains(ChangeMessagesUtil.TAG_SET_WIP);
+
+    gApi.changes().id(changeId).setReadyForReview("PTAL");
+
+    info = gApi.changes().id(changeId).get();
+    assertThat(info.workInProgress).isFalse();
+    assertThat(Iterables.getLast(info.messages).message).contains("PTAL");
+    assertThat(Iterables.getLast(info.messages).tag).contains(ChangeMessagesUtil.TAG_SET_READY);
+
+    // No message
+    gApi.changes().id(changeId).setWorkInProgress();
+
+    info = gApi.changes().id(changeId).get();
+
+    assertThat(info.workInProgress).isTrue();
+    assertThat(Iterables.getLast(info.messages).message).isEqualTo("Set Work In Progress");
+    assertThat(Iterables.getLast(info.messages).tag).contains(ChangeMessagesUtil.TAG_SET_WIP);
+
+    gApi.changes().id(changeId).setReadyForReview();
+
+    info = gApi.changes().id(changeId).get();
+    assertThat(info.workInProgress).isFalse();
+    assertThat(Iterables.getLast(info.messages).message).isEqualTo("Set Ready For Review");
+    assertThat(Iterables.getLast(info.messages).tag).contains(ChangeMessagesUtil.TAG_SET_READY);
+  }
+
+  @Test
   public void getAmbiguous() throws Exception {
     PushOneCommit.Result r1 = createChange();
     String changeId = r1.getChangeId();
diff --git a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/api/changes/ChangeApi.java b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/api/changes/ChangeApi.java
index 83d36b1..8d7a452 100644
--- a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/api/changes/ChangeApi.java
+++ b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/api/changes/ChangeApi.java
@@ -87,6 +87,18 @@
 
   void setPrivate(boolean value) throws RestApiException;
 
+  void setWorkInProgress(String message) throws RestApiException;
+
+  void setReadyForReview(String message) throws RestApiException;
+
+  default void setWorkInProgress() throws RestApiException {
+    setWorkInProgress(null);
+  }
+
+  default void setReadyForReview() throws RestApiException {
+    setReadyForReview(null);
+  }
+
   /**
    * Ignore or un-ignore this change.
    *
@@ -328,6 +340,16 @@
     }
 
     @Override
+    public void setWorkInProgress(String message) {
+      throw new NotImplementedException();
+    }
+
+    @Override
+    public void setReadyForReview(String message) {
+      throw new NotImplementedException();
+    }
+
+    @Override
     public ChangeApi revert() {
       throw new NotImplementedException();
     }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/api/changes/ChangeApiImpl.java b/gerrit-server/src/main/java/com/google/gerrit/server/api/changes/ChangeApiImpl.java
index c6fc67e..f75adbc 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/api/changes/ChangeApiImpl.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/api/changes/ChangeApiImpl.java
@@ -75,10 +75,13 @@
 import com.google.gerrit.server.change.Revert;
 import com.google.gerrit.server.change.Reviewers;
 import com.google.gerrit.server.change.Revisions;
+import com.google.gerrit.server.change.SetReadyForReview;
+import com.google.gerrit.server.change.SetWorkInProgress;
 import com.google.gerrit.server.change.SubmittedTogether;
 import com.google.gerrit.server.change.SuggestChangeReviewers;
 import com.google.gerrit.server.change.Unignore;
 import com.google.gerrit.server.change.Unmute;
+import com.google.gerrit.server.change.WorkInProgressOp;
 import com.google.gerrit.server.permissions.PermissionBackendException;
 import com.google.gerrit.server.project.InvalidChangeOperationException;
 import com.google.gerrit.server.update.UpdateException;
@@ -136,6 +139,8 @@
   private final Unignore unignore;
   private final Mute mute;
   private final Unmute unmute;
+  private final SetWorkInProgress setWip;
+  private final SetReadyForReview setReady;
 
   @Inject
   ChangeApiImpl(
@@ -177,6 +182,8 @@
       Unignore unignore,
       Mute mute,
       Unmute unmute,
+      SetWorkInProgress setWip,
+      SetReadyForReview setReady,
       @Assisted ChangeResource change) {
     this.changeApi = changeApi;
     this.revert = revert;
@@ -216,6 +223,8 @@
     this.unignore = unignore;
     this.mute = mute;
     this.unmute = unmute;
+    this.setWip = setWip;
+    this.setReady = setReady;
     this.change = change;
   }
 
@@ -310,6 +319,24 @@
   }
 
   @Override
+  public void setWorkInProgress(String message) throws RestApiException {
+    try {
+      setWip.apply(change, new WorkInProgressOp.Input(message));
+    } catch (UpdateException e) {
+      throw new RestApiException("Cannot set work in progress state", e);
+    }
+  }
+
+  @Override
+  public void setReadyForReview(String message) throws RestApiException {
+    try {
+      setReady.apply(change, new WorkInProgressOp.Input(message));
+    } catch (UpdateException e) {
+      throw new RestApiException("Cannot set ready for review state", e);
+    }
+  }
+
+  @Override
   public ChangeApi revert() throws RestApiException {
     return revert(new RevertInput());
   }
diff --git a/gerrit-server/src/test/java/com/google/gerrit/server/query/change/AbstractQueryChangesTest.java b/gerrit-server/src/test/java/com/google/gerrit/server/query/change/AbstractQueryChangesTest.java
index f023eb5..34ef0b5 100644
--- a/gerrit-server/src/test/java/com/google/gerrit/server/query/change/AbstractQueryChangesTest.java
+++ b/gerrit-server/src/test/java/com/google/gerrit/server/query/change/AbstractQueryChangesTest.java
@@ -412,6 +412,48 @@
   }
 
   @Test
+  public void byWip() throws Exception {
+    TestRepository<Repo> repo = createProject("repo");
+    Change change1 = insert(repo, newChange(repo), userId);
+
+    assertQuery("is:open", change1);
+    assertQuery("is:wip");
+
+    gApi.changes().id(change1.getChangeId()).setWorkInProgress();
+
+    assertQuery("is:wip", change1);
+
+    gApi.changes().id(change1.getChangeId()).setReadyForReview();
+
+    assertQuery("is:wip");
+  }
+
+  @Test
+  public void excludeWipChangeFromReviewersDashboards() throws Exception {
+    Account.Id user1 = createAccount("user1");
+    TestRepository<Repo> repo = createProject("repo");
+    Change change1 = insert(repo, newChange(repo), userId);
+
+    AddReviewerInput rin = new AddReviewerInput();
+    rin.reviewer = user1.toString();
+    rin.state = ReviewerState.REVIEWER;
+    gApi.changes().id(change1.getId().get()).addReviewer(rin);
+
+    assertQuery("is:wip");
+    assertQuery("reviewer:" + user1, change1);
+
+    gApi.changes().id(change1.getChangeId()).setWorkInProgress();
+
+    assertQuery("is:wip", change1);
+    assertQuery("reviewer:" + user1);
+
+    gApi.changes().id(change1.getChangeId()).setReadyForReview();
+
+    assertQuery("is:wip");
+    assertQuery("reviewer:" + user1, change1);
+  }
+
+  @Test
   public void byCommit() throws Exception {
     TestRepository<Repo> repo = createProject("repo");
     ChangeInserter ins = newChange(repo);